gastownhall/beads · error
applyGraph: edge %d %s->%s: checking planned blocking cycle:
Error message
applyGraph: edge %d %s->%s: checking planned blocking cycle: %w
What it means
While checking whether a planned blocking edge would close a cycle, the helper graphHasPath returned an error; this wraps it with the edge's index and endpoints. The plan is rejected because cycle safety could not be verified — the cause is in the wrapped error (storage/query failure inside the path search).
Source
Thrown at internal/storage/domain/issue.go:1481
continue
}
fromID := resolveEdgeRef(edge.FromKey, edge.FromID, keyToID)
toID := resolveEdgeRef(edge.ToKey, edge.ToID, keyToID)
if fromID == "" || toID == "" {
continue
}
if fromID == toID {
return fmt.Errorf("applyGraph: edge %d %s->%s creates a blocking dependency cycle", i, fromID, toID)
}
adj[fromID] = append(adj[fromID], toID)
checks = append(checks, plannedEdge{index: i, fromID: fromID, toID: toID})
}
depCache := make(map[string][]*types.Dependency)
for _, edge := range checks {
hasPath, err := u.graphHasPath(ctx, adj, depCache, edge.toID, edge.fromID, cycleRelevantDepType)
if err != nil {
return fmt.Errorf("applyGraph: edge %d %s->%s: checking planned blocking cycle: %w", edge.index, edge.fromID, edge.toID, err)
}
if hasPath {
return fmt.Errorf("applyGraph: edge %d %s->%s creates a blocking dependency cycle", edge.index, edge.fromID, edge.toID)
}
}
return nil
}
// graphHasPath returns true if fromID can reach toID by following the
// in-memory adjacency (planned parent-child + planned blocking edges) and
// existing deps loaded lazily from the store. followExistingDep selects which
// existing dep types the walk traverses, so callers can mirror either the
// early blocking-only preflight or the broader ready-work graph. Per-node dep
// fetches are cached so each visited node hits the DB at most once.
//
// Existing deps are loaded from BOTH dependency tables. The per-edge
// depRepo.HasCycle probe this walk replaced traversed dependencies ∪
// wisp_dependencies (and the embedded path's GetDependencyRecords selects theView on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped error for the underlying query/storage cause.
- Retry once the database is healthy.
- Trim graph size or split the plan into smaller applies if the path search times out.
- Validate existing dependency table consistency before applying.
Defensive patterns
Strategy: retry
Validate before calling
// Local reachability pre-check mirrors the server-side check
for _, e := range plan.Edges {
if reachable(adj, e.To, e.From) {
return fmt.Errorf("edge %s->%s would close a cycle", e.From, e.To)
}
} Try / catch
if err := bd.GraphApply(ctx, plan); err != nil {
if strings.Contains(err.Error(), "checking planned blocking cycle") {
// validation infrastructure failed, not the plan — retry after DB recovers
return retryWithBackoff(func() error { return bd.GraphApply(ctx, plan) })
}
return err
} Prevention
- Verify DB health before large applies
- Pre-compute cycles locally to reduce reliance on server-side path searches
- Cache dep lookups when validating many edges
When it happens
Trigger: graphHasPath's dependency lookups (via depCache against depRepo) fail — Dolt query error, connectivity issue, or malformed dependency rows — while validating edge from->to for reachability toID->fromID.
Common situations: Database unavailable mid-validation; extremely deep/large graphs causing timeouts in the path search; inconsistent dep rows causing lookup errors.
Related errors
- applyGraph: final cycle check: %w
- get molecule children: %w
- failed to get issue %s: %w
- failed to remove relates-to %s -> %s: %w
- failed to get epic: %v
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e67e49fa0d4043a4.
Report an issue: GitHub.