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 the

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped error for the underlying query/storage cause.
  2. Retry once the database is healthy.
  3. Trim graph size or split the plan into smaller applies if the path search times out.
  4. 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

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


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