gastownhall/beads · error
recompute-blocked: %w
Error message
recompute-blocked: %w
What it means
ops.recompute failed with an error that is not a 'dirty graph' race. Dirty-graph errors (another process's uncommitted edit between pull and repair) are retried as transients; all other recompute failures abort the sync loop as hard errors.
Source
Thrown at cmd/bd/sync.go:328
// through to the hard error below.
if isPullBehindFastForwardableErr(pullErr) {
out.Transients = append(out.Transients, syncTransient{
Attempt: attempt, Kind: syncTransientPullBehind, Error: pullErr.Error(),
})
ops.report("pull behind a fast-forwardable remote tip (peer pushed after our fetch) — re-pulling and retrying")
continue
}
return out, fmt.Errorf("pull: %w", pullErr)
}
if conflictErr != nil {
return out, fmt.Errorf("conflict check: %w", conflictErr)
}
ops.report("recompute-blocked")
corrected, err := ops.recompute(ctx)
if err != nil {
if !isRecomputeDirtyGraphErr(err) {
return out, fmt.Errorf("recompute-blocked: %w", err)
}
// Not our failure and not a durable one: someone else's
// uncommitted edit to issues/dependencies landed between our pull
// and our repair. Treat it exactly like a push race — re-enter the
// attempt loop, and if the budget runs out report the transient
// exit so the next tick tries again. Classifying it as a hard
// error instead left local commits unpublished until a tick
// happened to catch a clean working set, which on a shared
// sql-server topology is luck (wy-mlnz2).
//
// Two things about the retry are worth knowing before touching it.
// It is paced by the pull's round trip, not by a sleep — the loop
// has none. And the retry does not merely WAIT for the other
// writer: the pull's own pre-merge auto-commit (GH#2474) stages and
// commits whatever is dirty, so it is often what clears the guard,
// committing that writer's already-SQL-committed rows under this
// sync's author. That is pre-existing behavior on attempt 1 of
// every tick and is data-safe, but a retry repeats the exposure —View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped error to find which graph invariant failed
- Fix or remove the offending dependency/issue rows (bd dep remove / bd doctor)
- Restore the DB from .beads/issues.jsonl export or git history if the graph is corrupted
- Retry sync after ensuring no other bd process holds the DB
Defensive patterns
Strategy: try-catch
Validate before calling
// validate the dependency graph before syncing
for _, edge := range allDeps {
if !issueExists(edge.IssueID) || !issueExists(edge.DependsOnID) {
return fmt.Errorf("dangling dependency %s -> %s; repair before sync", edge.IssueID, edge.DependsOnID)
}
} Try / catch
out, err := runSyncCommand(ctx, opts)
if err != nil {
var recErr *RecomputeError
if errors.As(err, &recErr) && !isDirtyGraphErr(recErr) {
// hard failure: inspect graph corruption, run bd doctor
}
return err
} Prevention
- Don't hand-edit DB rows or issues.jsonl while bd is running
- Run `bd doctor` after manual edits or interrupted migrations
- Ensure adequate disk space for recompute writes
- Check that no other process holds uncommitted edits during sync
When it happens
Trigger: The graph-repair/recompute step hits a real failure: corrupted dependency graph rows, storage write error, constraint violation during repair, or context cancellation.
Common situations: Manually edited issues.jsonl or DB rows leaving the dependency graph inconsistent; disk-full during recompute; bug in a migration that produced edges pointing at nonexistent issues.
Related errors
- failed to set sync remote: %w
- epic '%s' not found
- issue '%s' not found
- swarm molecule '%s' has no linked epic
- conflict check: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/61f54a231e0e4269.
Report an issue: GitHub.