gastownhall/beads · error

add dep: IssueID and DependsOnID must be non-empty

Error message

add dep: IssueID and DependsOnID must be non-empty

What it means

A validation error from the dependency use case: the Dependency struct was non-nil but IssueID or DependsOnID was empty. A dependency edge requires both endpoints, so the call is rejected before any repository or cycle checks run.

Solutions

  1. Set both IssueID and DependsOnID on the Dependency before calling
  2. Validate both IDs are non-empty in the caller prior to the API call
  3. Fix the data source (CLI args, JSON, env) that yielded an empty ID

Example fix

// before
dep := &types.Dependency{IssueID: issueID} // DependsOnID missing
err := uc.AddWispDependency(ctx, dep, actor)
// after
if issueID == "" || dependsOnID == "" { return fmt.Errorf("both issue IDs required") }
dep := &types.Dependency{IssueID: issueID, DependsOnID: dependsOnID}
err := uc.AddWispDependency(ctx, dep, actor)
Defensive patterns

Strategy: validation

Validate before calling

if dep == nil || dep.IssueID == "" || dep.DependsOnID == "" {
    return fmt.Errorf("dependency requires non-empty IssueID and DependsOnID")
}

Type guard

func isCompleteDependency(dep *types.Dependency) bool {
    return dep != nil && dep.IssueID != "" && dep.DependsOnID != ""
}

Prevention

When it happens

Trigger: Calling AddDependency/AddWispDependency with a &types.Dependency{} where IssueID=="" or DependsOnID=="" — e.g. fields not set after construction, or sourced from empty CLI flags/env/JSON fields.

Common situations: Script or command omitted one of the two issue IDs; parsing a dependency record with a missing field; empty variable interpolation in a shell pipeline feeding bd.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/a8a99b0456d31cad. Report an issue: GitHub.

Appendix: source

Thrown at internal/storage/domain/dependency.go:276

	depRepo DependencySQLRepository
}

var _ DependencyUseCase = (*dependencyUseCaseImpl)(nil)

func (u *dependencyUseCaseImpl) AddDependency(ctx context.Context, dep *types.Dependency, actor string) error {
	return u.add(ctx, dep, actor, false)
}

func (u *dependencyUseCaseImpl) AddWispDependency(ctx context.Context, dep *types.Dependency, actor string) error {
	return u.add(ctx, dep, actor, true)
}

func (u *dependencyUseCaseImpl) add(ctx context.Context, dep *types.Dependency, actor string, useWisp bool) error {
	if dep == nil {
		return fmt.Errorf("add dep: dep must not be nil")
	}
	if dep.IssueID == "" || dep.DependsOnID == "" {
		return fmt.Errorf("add dep: IssueID and DependsOnID must be non-empty")
	}

	// Self-dependency guard mirrors issueops.CheckDependencyCycleInTx: it is
	// checked BEFORE the cycle probe and for ALL dep types, and emits the
	// dedicated self-dep message. A blocking self-edge otherwise trips HasCycle
	// and would report the wrong (cycle) error (#4547 F-1).
	if dep.IssueID == dep.DependsOnID {
		return fmt.Errorf("%w: %s cannot depend on itself", ErrSelfDependency, dep.IssueID)
	}
	if err := u.depRepo.ValidateBlockingHierarchy(ctx, dep); err != nil {
		var hierarchyConflict *DependencyHierarchyConflictError
		if errors.As(err, &hierarchyConflict) {
			return err
		}
		return fmt.Errorf("add dep: hierarchy check: %w", err)
	}

	if types.IsSchedulingEdge(dep.Type) {

View on GitHub (pinned to 71377f2769)