gastownhall/beads · error
final graph cycle check: %w
Error message
final graph cycle check: %w
What it means
After all dependencies are written, graph apply runs tx.CycleThroughEdges over the newly added scheduling edges as a final safety net. This error wraps a failure of that check itself (the cycle detector errored), as opposed to a cycle actually being found — usually a storage/query problem while walking dependency edges.
Source
Thrown at cmd/bd/graph_apply.go:1086
}
if (depType == types.DepParentChild) != parentPhase {
continue
}
d, err := types.NewGraphNodeDependency(issues[i].ID, depType, dep.Target, keyToID)
if err != nil {
return fmt.Errorf("node %q: %w", node.Key, err)
}
if err := tx.AddDependency(ctx, d, actor); err != nil {
return fmt.Errorf("node %q: adding dep to %q: %w", node.Key, dep.Target, err)
}
if graphApplySchedulingDependencyType(d.Type) {
newSchedulingEdges = append(newSchedulingEdges, [2]string{d.IssueID, d.DependsOnID})
}
}
}
}
if cyclePath, err := tx.CycleThroughEdges(ctx, newSchedulingEdges); err != nil {
return fmt.Errorf("final graph cycle check: %w", err)
} else if cyclePath != "" {
return fmt.Errorf("graph dependency cycle would be created: %s", cyclePath)
}
// Apply deferred assignees.
for i, assignee := range pendingAssignees {
updates := map[string]interface{}{
"assignee": assignee,
}
if err := tx.UpdateIssue(ctx, issues[i].ID, updates, actor); err != nil {
return fmt.Errorf("node %q: setting assignee: %w", plan.Nodes[i].Key, err)
}
}
return nil
}); err != nil {
return nil, err
}View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped error for the storage-level cause and retry the apply
- Check database health (bd doctor) and connectivity
- Reduce plan size if the cycle query times out on very large graphs
- Re-run after resolving any concurrent-writer conflicts
Example fix
// before: applying a 10k-node graph in one transaction times out bd graph apply huge-plan.json // after: split into smaller plans bd graph apply part1.json && bd graph apply part2.json
Defensive patterns
Strategy: retry
Validate before calling
// no caller-side check can replace the in-transaction cycle query; ensure DB health first
if err := db.Ping(); err != nil { return err } Try / catch
if err := bd.GraphApply(ctx, plan); err != nil {
if strings.Contains(err.Error(), "final graph cycle check") {
return retryWithBackoff(ctx, func() error { _, err := bd.GraphApply(ctx, plan); return err })
}
return err
} Prevention
- Verify storage health (bd doctor) before very large applies
- Split huge plans into smaller batches to reduce query load
- Avoid flaky remote storage connections for apply operations
When it happens
Trigger: tx.CycleThroughEdges(ctx, newSchedulingEdges) returns a non-nil error after all deps were inserted, e.g. the storage backend failed to query the dependency graph.
Common situations: Storage connectivity drops mid-transaction, very large graphs exhausting query limits, or backend-specific query failures.
Related errors
- node %q: adding parent-child dep: %w
- adding edge %s->%s: %w
- node %q: adding dep to %q: %w
- node %q: setting assignee: %w
- graph create: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/dae285f56d475aa7.
Report an issue: GitHub.