gastownhall/beads · error
add deps[%d]: cycle check: %w
Error message
add deps[%d]: cycle check: %w
What it means
For each scheduling edge (when SkipPerEdgeCycleCheck is false), AddDependencies calls HasCycle to test whether the proposed edge would close a cycle. This error wraps a failure of the HasCycle query itself — the cycle detection could not be executed, so the batch is aborted rather than risking an unchecked write. Note: an actual detected cycle is a different, cycleErrorf-typed error ('would create a cycle').
Source
Thrown at internal/storage/domain/dependency.go:735
// 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{
UseWispsTable: sourceIsWisp,
HierarchyValidated: true,
CycleValidated: true,
EmitEvent: true,
}); err != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped error after 'cycle check:' for the storage root cause
- Retry with a longer context deadline if the graph is large
- If cycles are verified separately, set opts.SkipPerEdgeCycleCheck=true to rely on the final CycleThroughEdges check instead
Defensive patterns
Strategy: validation
Validate before calling
// Optionally skip the per-edge probe when you pre-verified acyclicity: opts.SkipPerEdgeCycleCheck = true // rely on final CycleThroughEdges instead
Try / catch
if err != nil && strings.Contains(err.Error(), "cycle check") {
// HasCycle query failed (not a detected cycle); inspect wrapped cause, retry
} Prevention
- Distinguish detected cycles (cycleErrorf message) from check failures (this wrapper)
- Use generous context deadlines for large graphs
- Avoid concurrent writers during big dependency imports
When it happens
Trigger: Calling AddDependencies with opts.SkipPerEdgeCycleCheck=false and a scheduling dep type, when depRepo.HasCycle(ctx, IssueID, DependsOnID) returns a storage/query error.
Common situations: Slow or locked Dolt database during deep graph traversal; context deadline exceeded on very large dependency graphs; transient DB connection drops mid-batch.
Related errors
- add deps: final cycle check: %w
- loading deps for %s: %w
- failed to remove relates-to %s -> %s: %w
- failed to get dependencies for %s: %w
- db: DependencySQLRepository.HasCycle: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/7761b709f5885aea.
Report an issue: GitHub.