gastownhall/beads · error

applyGraph: final cycle check: %w

Error message

applyGraph: final cycle check: %w

What it means

After all nodes and edges are inserted, applyGraph runs a final CycleThroughEdges check over the newly added scheduling edges; this error wraps a failure of that cycle-detection query itself (not the discovery of a cycle). The apply is aborted because the safety check could not complete.

Source

Thrown at internal/storage/domain/issue.go:1292

			for _, nd := range node.Deps {
				dep, err := types.NewGraphNodeDependency(keyToID[node.Key], nd.Type, nd.Target, keyToID)
				if err != nil {
					return GraphApplyResult{}, fmt.Errorf("applyGraph: node %q: %w", node.Key, err)
				}
				if (dep.Type == types.DepParentChild) != parentPhase {
					continue
				}
				if err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{UseWispsTable: useWisp}); err != nil {
					return GraphApplyResult{}, fmt.Errorf("applyGraph: node %q: adding dep to %q: %w", node.Key, nd.Target, err)
				}
				if types.IsSchedulingEdge(dep.Type) {
					newSchedulingEdges = append(newSchedulingEdges, [2]string{dep.IssueID, dep.DependsOnID})
				}
			}
		}
	}
	if cyclePath, err := u.depRepo.CycleThroughEdges(ctx, newSchedulingEdges); err != nil {
		return GraphApplyResult{}, fmt.Errorf("applyGraph: final cycle check: %w", err)
	} else if cyclePath != "" {
		return GraphApplyResult{}, fmt.Errorf("applyGraph: dependency cycle would be created: %s", cyclePath)
	}

	// Pass 5 — apply deferred assignees.
	for i, assignee := range pendingAssignees {
		if assignee == "" {
			continue
		}
		id := keyToID[plan.Nodes[i].Key]
		if err := u.issueRepo.Update(ctx, id, map[string]any{"assignee": assignee}, actor, IssueTableOpts{UseWispsTable: useWisp}); err != nil {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: node %q: defer assignee: %w", plan.Nodes[i].Key, err)
		}
	}

	return GraphApplyResult{IDs: keyToID}, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped error to see whether it's connectivity, timeout, or a query error.
  2. Retry the apply once the database is reachable (inserted edges may need rollback/cleanup if the apply is not transactional).
  3. For large graphs, reduce batch size or ensure the dep table indexes support the cycle query.
  4. Verify dependency table integrity (bd doctor / storage checks) if errors persist.
Defensive patterns

Strategy: retry

Validate before calling

// Cheap local cycle pre-check on the plan's scheduling edges before apply
if cycle := findCycle(planEdgesToAdjacency(plan.Edges)); cycle != "" {
    return fmt.Errorf("plan has cycle: %s", cycle)
}

Try / catch

if err := bd.GraphApply(ctx, plan); err != nil {
    if strings.Contains(err.Error(), "final cycle check") {
        // transient storage failure during validation — safe to retry after cleanup
        return retryWithBackoff(func() error { return bd.GraphApply(ctx, plan) })
    }
    return err
}

Prevention

When it happens

Trigger: CycleThroughEdges returning a query/storage error — Dolt query failure, connectivity problem, or an internal error traversing the dependency graph — during the final validation pass of graph apply.

Common situations: Database connectivity drops mid-apply; very large graphs timing out the cycle query; corrupted or inconsistent dependency tables; running against a Dolt server under load.

Related errors


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