gastownhall/beads · error

add deps[%d]: hierarchy check: %w

Error message

add deps[%d]: hierarchy check: %w

What it means

Before inserting each blocking edge, AddDependencies calls ValidateBlockingHierarchy on the repository. This error wraps any failure of that check other than a *DependencyHierarchyConflictError (which is returned unwrapped so callers can errors.As it). It means the hierarchy validation itself failed to run (storage error) or returned a non-typed error.

Source

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

	wispSources, err := u.depRepo.WispSourceIDs(ctx, sources)
	if err != nil {
		return BulkAddDepsResult{}, fmt.Errorf("add deps: classify sources: %w", err)
	}
	// Parent-child edges must be visible before blocking edges in the same
	// request. The shared repository guard can then evaluate existing + planned
	// ancestry without widening #4034 into #4035's combined-graph cycle check.
	for phase := 0; phase < 2; phase++ {
		parentPhase := phase == 0
		for i, dep := range deps {
			if (dep.Type == types.DepParentChild) != parentPhase {
				continue
			}
			if err := u.depRepo.ValidateBlockingHierarchy(ctx, dep); err != nil {
				var hierarchyConflict *DependencyHierarchyConflictError
				if errors.As(err, &hierarchyConflict) {
					return BulkAddDepsResult{}, err
				}
				return BulkAddDepsResult{}, fmt.Errorf("add deps[%d]: hierarchy check: %w", i, err)
			}
			if !opts.SkipPerEdgeCycleCheck && types.IsSchedulingEdge(dep.Type) {
				cycle, err := u.depRepo.HasCycle(ctx, dep.IssueID, dep.DependsOnID)
				if err != nil {
					return BulkAddDepsResult{}, fmt.Errorf("add deps[%d]: cycle check: %w", i, err)
				}
				if cycle {
					return BulkAddDepsResult{}, cycleErrorf("add deps[%d]: adding %s -> %s would create a cycle", i, dep.IssueID, dep.DependsOnID)
				}
			}
			// The explicit `bd dep add` / `bd link` verb on the proxied server
			// (cmd/bd/dep_proxied_server.go, link_proxied_server.go) records a
			// dependency_added event for each genuine new edge — unlike
			// create-with-deps, which calls depRepo.Insert directly without
			// EmitEvent. UseWispsTable routes both the edge and that event to
			// the source's own pair of tables.
			_, sourceIsWisp := wispSources[dep.IssueID]
			if err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped error after the 'hierarchy check:' prefix to find the real cause
  2. If you expected a hierarchy conflict (e.g. blocking a child against its parent), match *DependencyHierarchyConflictError via errors.As — that path returns the typed error directly
  3. Retry after confirming database health (bd doctor)
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check parent-child relationships so blocking edges you know conflict
// with ancestry are not submitted at all

Type guard

var hc *domain.DependencyHierarchyConflictError
if errors.As(err, &hc) { /* typed hierarchy conflict: handle specifically */ }

Try / catch

if err != nil {
    var hc *domain.DependencyHierarchyConflictError
    if errors.As(err, &hc) { return err } // typed path
    // otherwise wrapped as 'hierarchy check:' — storage issue, inspect cause
}

Prevention

When it happens

Trigger: Calling AddDependencies with a scheduling/blocking edge where depRepo.ValidateBlockingHierarchy returns an error that is not a DependencyHierarchyConflictError — e.g. a query failure while walking parent-child ancestry.

Common situations: Storage backend errors during ancestry traversal; partially corrupted dependency tables; context cancellation mid-hierarchy-walk on a large graph.

Related errors


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