gastownhall/beads · error

db: DependencySQLRepository.ValidateBlockingHierarchy: dep m

Error message

db: DependencySQLRepository.ValidateBlockingHierarchy: dep must not be nil

What it means

ValidateBlockingHierarchy returns this error when the dep argument is nil. After the nil guard it skips validation for external dependency targets and otherwise runs CheckBlockingHierarchyInTx to ensure adding the dependency would not create an invalid blocking hierarchy (e.g. a cycle or a blocker violating hierarchy rules). Called by Insert during dependency creation.

Source

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

}

func (r *dependencySQLRepositoryImpl) rowExists(ctx context.Context, table, id string) (bool, error) {
	var probe int
	//nolint:gosec // G201: table is one of the two hardcoded plane tables
	err := r.runner.QueryRowContext(ctx, fmt.Sprintf("SELECT 1 FROM %s WHERE id = ? LIMIT 1", table), id).Scan(&probe)
	switch {
	case err == nil:
		return true, nil
	case errors.Is(err, sql.ErrNoRows):
		return false, nil
	default:
		return false, err
	}
}

func (r *dependencySQLRepositoryImpl) ValidateBlockingHierarchy(ctx context.Context, dep *types.Dependency) error {
	if dep == nil {
		return errors.New("db: DependencySQLRepository.ValidateBlockingHierarchy: dep must not be nil")
	}
	if issueops.IsExternalDepTarget(dep.IssueID, dep.DependsOnID) {
		return nil
	}
	return issueops.CheckBlockingHierarchyInTx(ctx, r.runner, dep, nil)
}

// markDirectBlockedSource mirrors issueops.markDirectBlockingDependencySourceInTx:
// is_blocked is derived state, and ready-work queries filter on it directly
// (is_blocked = 0), so a blocking edge insert must set it on the source row
// while the target is still open. updated_at is pinned because recomputing
// derived state is not an edit.
func (r *dependencySQLRepositoryImpl) markDirectBlockedSource(ctx context.Context, source string, srcIsWisp bool, target, targetCol string) error {
	sourceTable := "issues"
	if srcIsWisp {
		sourceTable = "wisps"
	}
	var targetTable string

View on GitHub (pinned to 71377f2769)

Solutions

  1. Ensure callers construct a valid *types.Dependency before validation/insert.
  2. Fix the upstream nil-producing path (failed lookup passed through unchecked).
  3. For external deps, confirm IsExternalDepTarget applies — validation is skipped for those, but nil is still rejected.

Example fix

// before
repo.ValidateBlockingHierarchy(ctx, nil)
// after
if dep == nil {
    return fmt.Errorf("no dependency to validate")
}
repo.ValidateBlockingHierarchy(ctx, dep)
Defensive patterns

Strategy: validation

Validate before calling

if dep == nil {
    return fmt.Errorf("dependency required for hierarchy validation")
}

Type guard

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

Try / catch

if err := repo.ValidateBlockingHierarchy(ctx, dep); err != nil {
    if strings.Contains(err.Error(), "dep must not be nil") {
        // construct dependency before validating
    }
    // other errors: blocking-hierarchy violations
}

Prevention

When it happens

Trigger: Insert (or a direct call) passes a nil *types.Dependency to ValidateBlockingHierarchy — same root causes as the nil-Insert case: uninitialized dep pointer from a conditional build path or unchecked lookup.

Common situations: Code path where dependency creation succeeded partially and validation was invoked with a nil record; refactored call sites dropping the nil check upstream.

Related errors


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