gastownhall/beads · error
add dep: cycle check: %w
Error message
add dep: cycle check: %w
What it means
Wraps a repository failure that occurred while running the cycle-detection probe (depRepo.HasCycle) during dependency creation. Beads refuses scheduling-type dependency edges (e.g. blocks/blocked-by) that would form a cycle, and this wrapper only fires when the probe itself errored — not when a cycle was actually found (that returns the sentinel ErrDependencyCycle instead). It means the cycle check could not complete, typically due to a storage/backend problem.
Source
Thrown at internal/storage/domain/dependency.go:297
// Self-dependency guard mirrors issueops.CheckDependencyCycleInTx: it is
// checked BEFORE the cycle probe and for ALL dep types, and emits the
// dedicated self-dep message. A blocking self-edge otherwise trips HasCycle
// and would report the wrong (cycle) error (#4547 F-1).
if dep.IssueID == dep.DependsOnID {
return fmt.Errorf("%w: %s cannot depend on itself", ErrSelfDependency, dep.IssueID)
}
if err := u.depRepo.ValidateBlockingHierarchy(ctx, dep); err != nil {
var hierarchyConflict *DependencyHierarchyConflictError
if errors.As(err, &hierarchyConflict) {
return err
}
return fmt.Errorf("add dep: hierarchy check: %w", err)
}
if types.IsSchedulingEdge(dep.Type) {
cycle, err := u.depRepo.HasCycle(ctx, dep.IssueID, dep.DependsOnID)
if err != nil {
return fmt.Errorf("add dep: cycle check: %w", err)
}
if cycle {
// Match the embedded store's user-facing wording verbatim (no ids
// prefix) so gc code that string-matches this error behaves the same
// on both plumbings (#4547 F-1).
return ErrDependencyCycle
}
}
if err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{UseWispsTable: useWisp, HierarchyValidated: true, CycleValidated: true, EmitEvent: true}); err != nil {
// The retype conflict is a user-facing error whose message already
// matches embedded verbatim; pass it through unwrapped so the CLI does
// not prepend "add dep: insert:" (#4547 F-1). The endpoint-existence
// refusals are here for the same reason.
var conflict *DependencyTypeConflictError
if errors.As(err, &conflict) {
return err
}View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped %w cause with errors.Unwrap / %+v to see the underlying repository error and fix that root cause first.
- Verify the database backend is reachable and healthy (bd doctor, or reconnect the Dolt server).
- Retry the operation once the storage layer is healthy — the check is read-only and safe to re-run.
- If the cause is a context timeout, re-run with a longer deadline or smaller batch instead of bypassing the check.
Example fix
// before: swallowing the wrapped cause
if err := uc.AddDependency(ctx, dep, actor); err != nil {
log.Println("dep add failed")
}
// after: surface and classify the underlying cause
if err := uc.AddDependency(ctx, dep, actor); err != nil {
if errors.Is(err, domain.ErrDependencyCycle) {
log.Println("cycle rejected")
} else {
log.Printf("cycle check/storage failure: %v", err)
}
} Defensive patterns
Strategy: try-catch
Validate before calling
if dep == nil || dep.IssueID == "" || dep.DependsOnID == "" || dep.IssueID == dep.DependsOnID {
return fmt.Errorf("invalid dependency before add")
}
if !types.IsSchedulingEdge(dep.Type) {
// cycle check is skipped for non-scheduling edges
_ = dep
} Type guard
func isSchedulingDep(dep *types.Dependency) bool {
return dep != nil && dep.IssueID != "" && dep.DependsOnID != "" && types.IsSchedulingEdge(dep.Type)
} Try / catch
if err := uc.AddDependency(ctx, dep, actor); err != nil {
switch {
case errors.Is(err, domain.ErrDependencyCycle):
// cycle rejected — not this error
case errors.Is(err, context.DeadlineExceeded):
// storage timeout during cycle check; retry with longer deadline
default:
return fmt.Errorf("cycle check storage failure: %w", err)
}
} Prevention
- Pre-check client-side for obvious cycles (BFS on the scheduling graph) before issuing adds
- Keep DB connections healthy; batch adds inside one healthy session
- Use contexts with adequate deadlines for large graphs
- Never swallow the wrapped cause — unwrap to find the storage root cause
When it happens
Trigger: Calling AddDependency or AddWispDependency with a dep whose Type passes types.IsSchedulingEdge (e.g. blocks, parent-child scheduling semantics), where the underlying HasCycle query to the Dolt/sql repo fails — DB connection dropped, SQL error, context canceled mid-query, or lock/timeout in the dependency graph query.
Common situations: Database unavailable or restarting while a script batches bd dep add calls; context deadline exceeded on large dependency graphs; transient Dolt server errors during sync; running commands against a stale or locked .beads database.
Related errors
- loading proto: %w
- add dep: insert: %w
- remove dep: classify source: %w
- remove dep %s -> %s: %w
- reparent: list current parent: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/106f8a50fd3253db.
Report an issue: GitHub.