gastownhall/beads · error

add deps: final cycle check: %w

Error message

add deps: final cycle check: %w

What it means

After inserting edges individually, AddDependencies performs one combined CycleThroughEdges check over all non-scheduling pairs to catch cycles that only appear when the whole batch is considered. This error wraps a failure of that final query; a detected cycle instead yields the cycleErrorf message 'dependency cycle would be created: <path>'.

Source

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

				var missingEndpoint *DependencyEndpointNotFoundError
				if errors.As(err, &missingEndpoint) {
					return BulkAddDepsResult{}, err
				}
				return BulkAddDepsResult{}, fmt.Errorf("add deps[%d]: insert: %w", i, err)
			}
		}
	}
	var pairs [][2]string
	for _, dep := range deps {
		if !types.IsSchedulingEdge(dep.Type) {
			continue
		}
		pairs = append(pairs, [2]string{dep.IssueID, dep.DependsOnID})
	}
	if len(pairs) > 0 {
		cyclePath, err := u.depRepo.CycleThroughEdges(ctx, pairs)
		if err != nil {
			return BulkAddDepsResult{}, fmt.Errorf("add deps: final cycle check: %w", err)
		}
		if cyclePath != "" {
			return BulkAddDepsResult{}, cycleErrorf("add deps: dependency cycle would be created: %s", cyclePath)
		}
	}
	return BulkAddDepsResult{Added: deps}, nil
}

// ValidateBlockingHierarchy passes the edge straight to the repository check
// AddDependencies runs per edge, so a caller re-running the gate at the end of
// a mixed request raises the identical *DependencyHierarchyConflictError rather
// than a second opinion about the same graph.
func (u *dependencyUseCaseImpl) ValidateBlockingHierarchy(ctx context.Context, dep *types.Dependency) error {
	return u.depRepo.ValidateBlockingHierarchy(ctx, dep)
}

// CycleThroughEdges passes the pairs straight to the repository walk
// AddDependencies runs as its own final gate. The caller composes the refusal,

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause after 'final cycle check:'
  2. Retry with a healthy DB and non-canceled context
  3. Split very large batches into smaller requests to reduce traversal cost
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check: build the graph in memory and topologically verify the batch
// adds no cycle before calling AddDependencies

Try / catch

if err != nil && strings.Contains(err.Error(), "final cycle check") {
    // CycleThroughEdges query failed; inspect wrapped storage cause and retry
}

Prevention

When it happens

Trigger: Calling AddDependencies with at least one non-scheduling pair when depRepo.CycleThroughEdges(ctx, pairs) returns a storage/query error.

Common situations: Database errors during multi-edge graph traversal; context cancellation on large batches; locked or unavailable Dolt backend.

Related errors


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