{"record":{"id":"e6c23fe3b30e588e","repo":"gastownhall/beads","slug":"conflicts-resolved-but-is-blocked-recompute-failed","errorCode":null,"errorMessage":"conflicts resolved but is_blocked recompute failed: %w","messagePattern":"conflicts resolved but is_blocked recompute failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/federation.go","lineNumber":414,"sourceCode":"\t\t\t}\n\t\t}\n\t\tresult.ConflictsResolved = true\n\n\t\t// Commit the resolution INCLUDING config: the operator chose this\n\t\t// strategy, and plain Commit excludes config (GH#2455). A config-only\n\t\t// conflict — routine now that kv.memory.* memories sync through config —\n\t\t// would otherwise resolve but never commit, leaving the merge\n\t\t// unconcluded and re-wedging the next sync.\n\t\tif err := s.CommitMergeResolution(ctx, fmt.Sprintf(\"Resolve conflicts from %s using %s strategy\", peer, strategy)); err != nil {\n\t\t\tresult.Error = fmt.Errorf(\"failed to commit conflict resolution: %w\", err)\n\t\t\treturn result, result.Error\n\t\t}\n\n\t\t// bd-578h9.11: the conflicted merge skipped the automatic is_blocked\n\t\t// recompute (unresolved rows would have fed it garbage); now that the\n\t\t// resolution is committed, cover the whole merge+resolution window.\n\t\tif err := s.RecomputeBlockedAfterMerge(ctx, beforeCommit); err != nil {\n\t\t\tresult.Error = fmt.Errorf(\"conflicts resolved but is_blocked recompute failed: %w\", err)\n\t\t\treturn result, result.Error\n\t\t}\n\t}\n\tresult.Merged = true\n\n\t// Count pulled commits\n\tafterCommit, _ := s.GetCurrentCommit(ctx) // Best effort: empty commit hash means diff won't be logged\n\tif beforeCommit != afterCommit {\n\t\tresult.PulledCommits = 1 // Simplified - could count actual commits\n\t}\n\n\t// Step 5: Push our changes to peer, filtering excluded types.\n\texcludeTypes := config.GetFederationConfig().ExcludeTypes\n\tif err := s.filteredPushToPeer(ctx, peer, excludeTypes); err != nil {\n\t\t// Push failure is not fatal - peer may not accept pushes\n\t\tresult.PushError = err\n\t} else {\n\t\tresult.Pushed = true","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/federation.go#L396-L432","documentation":"After a conflicted merge is resolved and committed, Sync runs RecomputeBlockedAfterMerge(ctx, beforeCommit) to recompute is_blocked over the whole merge+resolution window (the conflicted merge had skipped the automatic recompute, per bd-578h9.11). This error means the data and conflicts are resolved but the derived is_blocked values may be stale or wrong until a recompute succeeds.","triggerScenarios":"store.Sync with strategy on a conflicted merge where resolution and CommitMergeResolution succeeded but RecomputeBlockedAfterMerge errors — SQL failure, timeout, or dependency-graph query failure during the recompute.","commonSituations":"Large merge windows (many changed dependencies) making the recompute slow enough to hit timeouts; DB contention right after commit; transient server restart mid-sync.","solutions":["Inspect the wrapped cause to find the failing recompute statement","Re-run sync — clean pulls trigger finishPeerPull's recompute path, and the recompute is idempotent","Manually trigger a blocked recompute (or run `bd doctor`-style consistency repair) to refresh is_blocked","Increase dolt server timeouts if the merge window is large","Check server logs around the sync timestamp for the exact query error"],"exampleFix":"// before: abort pipeline on recompute failure\nreturn fmt.Errorf(\"sync failed: %w\", result.Error)\n// after: data is synced; flag recompute for retry\nif result.Error != nil && strings.Contains(result.Error.Error(), \"is_blocked recompute failed\") {\n    log.Warn(\"synced; scheduling blocked-recompute retry\")\n    queueRecompute()\n    result.Error = nil\n}","handlingStrategy":"retry","validationCode":"if store == nil || isReadOnly() { skipRecomputeExpectations() }","typeGuard":"func isRecomputeFailure(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"is_blocked recompute failed\")\n}","tryCatchPattern":"result, err := store.Sync(ctx, peer, strategy, opts)\nif isRecomputeFailure(err) {\n    log.Warn(\"synced but recompute failed; retrying\")\n    queueRecompute() // idempotent re-run later\n}","preventionTips":["Schedule periodic idempotent recompute runs as a safety net","Raise DB timeouts for large merge windows","Avoid overlapping syncs that lengthen the merge window"],"tags":["dolt","federation","sync","recompute","consistency"],"backgroundTag":"post-merge-recompute-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}