gastownhall/beads · error

db: DependencySQLRepository.Insert: DependsOnID must not be

Error message

db: DependencySQLRepository.Insert: DependsOnID must not be empty

What it means

DependencySQLRepository.Insert returns this error when dep.DependsOnID is empty. A dependency must name the issue it depends on; an empty DependsOnID is rejected before any SQL executes. It is part of Insert's defensive validation chain (nil → IssueID → DependsOnID → self-dependency).

Source

Thrown at internal/storage/domain/db/dependency.go:79

		return "depends_on_wisp_id", nil
	case errors.Is(err, sql.ErrNoRows):
		return "depends_on_issue_id", nil
	case dberrors.IsTableNotExist(err):
		return "depends_on_issue_id", nil
	default:
		return "", fmt.Errorf("classify dep target %s: %w", dependsOnID, err)
	}
}

func (r *dependencySQLRepositoryImpl) Insert(ctx context.Context, dep *types.Dependency, actor string, opts domain.DepInsertOpts) error {
	if dep == nil {
		return errors.New("db: DependencySQLRepository.Insert: dep must not be nil")
	}
	if dep.IssueID == "" {
		return errors.New("db: DependencySQLRepository.Insert: IssueID must not be empty")
	}
	if dep.DependsOnID == "" {
		return errors.New("db: DependencySQLRepository.Insert: DependsOnID must not be empty")
	}
	if dep.IssueID == dep.DependsOnID {
		// Lead with the sentinel so this defensive repo-layer guard renders like
		// every other self-dep site ("cannot add self-dependency: X cannot depend
		// on itself") instead of appending the sentinel text.
		return fmt.Errorf("db: DependencySQLRepository.Insert: %w: %s cannot depend on itself", domain.ErrSelfDependency, dep.IssueID)
	}

	metadata := dep.Metadata
	if metadata == "" {
		metadata = "{}"
	}

	if !opts.HierarchyValidated {
		if err := r.ValidateBlockingHierarchy(ctx, dep); err != nil {
			return err
		}
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set dep.DependsOnID to the target issue's ID before calling Insert.
  2. Fix the CLI/input path so the target ID is always captured and validated early.
  3. Check for swapped IssueID/DependsOnID assignments.

Example fix

// before
dep := &types.Dependency{IssueID: parent.ID}
repo.Insert(ctx, dep, actor, opts)
// after
dep := &types.Dependency{IssueID: parent.ID, DependsOnID: target.ID}
repo.Insert(ctx, dep, actor, opts)
Defensive patterns

Strategy: validation

Validate before calling

if dep == nil || dep.DependsOnID == "" {
    return fmt.Errorf("DependsOnID required")
}

Try / catch

if err := repo.Insert(ctx, dep, actor, opts); err != nil {
    if strings.Contains(err.Error(), "DependsOnID must not be empty") {
        // populate DependsOnID and retry
    }
}

Prevention

When it happens

Trigger: Calling Insert with a *types.Dependency whose DependsOnID was never set — e.g. building the dependency from the owning issue's side only, or empty input from CLI/JSON where the target was omitted.

Common situations: CLI command invoked without the target issue argument; deserialization dropping the DependsOnID field; tests constructing minimal Dependency structs; fields swapped between IssueID and DependsOnID.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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