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

  1. Inspect the wrapped error for the storage-level cause and retry the apply
  2. Check database health (bd doctor) and connectivity
  3. Reduce plan size if the cycle query times out on very large graphs
  4. 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

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


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