gastownhall/beads · error

db: DependencySQLRepository.Insert: IssueID must not be empt

Error message

db: DependencySQLRepository.Insert: IssueID must not be empty

What it means

DependencySQLRepository.Insert returns this error when dep.IssueID is empty. The dependency must record which issue owns the dependency, so an empty IssueID is rejected before any SQL executes. It is a defensive repo-layer guard in the same validation chain as the nil and DependsOnID checks.

Source

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

	err := r.runner.QueryRowContext(ctx, "SELECT 1 FROM wisps WHERE id = ? LIMIT 1", dependsOnID).Scan(&probe)
	switch {
	case err == nil:
		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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set dep.IssueID to the owning issue's ID before calling Insert.
  2. Verify field order — if IssueID and DependsOnID were accidentally swapped, correct the assignment.
  3. Add upstream validation (service/command layer) that both IDs are present.

Example fix

// before
dep := &types.Dependency{DependsOnID: target.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.IssueID == "" {
    return fmt.Errorf("IssueID required")
}

Try / catch

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

Prevention

When it happens

Trigger: Calling Insert with a *types.Dependency whose IssueID field was never set — e.g. constructing the dependency from the 'depends-on' side only, or a partially filled struct from deserialization.

Common situations: Building dependencies from JSON/CLI input where the owning issue ID was omitted; swapping IssueID/DependsOnID by mistake; tests constructing minimal Dependency structs.

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/6be24088ccc32ed3. Report an issue: GitHub.