{"record":{"id":"5723fbd33ad973ae","repo":"gastownhall/beads","slug":"merge-succeeded-but-is-blocked-recompute-failed-5723fb","errorCode":null,"errorMessage":"merge succeeded but is_blocked recompute failed: %w","messagePattern":"merge succeeded but is_blocked recompute failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/embeddeddolt/version_control.go","lineNumber":315,"sourceCode":"func (s *EmbeddedDoltStore) Merge(ctx context.Context, branch string) ([]storage.Conflict, error) {\n\t// bd-578h9.11: like every pull path, a branch merge brings in writes that\n\t// bypassed the local is_blocked hooks; recompute after a conflict-free\n\t// merge. Conflicted merges defer to the caller's post-resolution hook\n\t// (Sync, bd vc merge --strategy) — recomputing over unresolved rows would\n\t// read garbage.\n\tpreHead := \"\"\n\tif !s.readOnly {\n\t\tpreHead = s.preMergeHead(ctx)\n\t}\n\tvar conflicts []storage.Conflict\n\terr := s.withMutatingDBConn(ctx, func(db versioncontrolops.DBConn) error {\n\t\tvar err error\n\t\tconflicts, err = versioncontrolops.Merge(ctx, db, branch, commitAuthor)\n\t\treturn err\n\t})\n\tif err == nil && len(conflicts) == 0 && !s.readOnly {\n\t\tif rerr := s.recomputeBlockedAfterPull(ctx, preHead); rerr != nil {\n\t\t\treturn conflicts, fmt.Errorf(\"merge succeeded but is_blocked recompute failed: %w\", rerr)\n\t\t}\n\t}\n\treturn conflicts, err\n}\n\n// MergeWithStrategy implements storage.StrategicMerger for `bd vc merge\n// --strategy` (#4992). Unlike Merge, it runs on a PINNED session\n// (withMutatingPinnedDBConn, not withMutatingDBConn): the conflict-tolerant\n// session flags versioncontrolops.MergeWithStrategy sets are session state\n// and must be visible to the merge, resolve, repair, and commit statements\n// that follow — a *sql.DB pool (OpenSQL allows 2 idle conns) could otherwise\n// hand out a different connection mid-sequence.\n//\n// A resolved merge (conflicted or clean) always commits, so — unlike plain\n// Merge, which skips the recompute for a still-conflicted merge — the\n// is_blocked recompute always runs on success here.\nfunc (s *EmbeddedDoltStore) MergeWithStrategy(ctx context.Context, branch, strategy string) ([]storage.Conflict, error) {\n\tpreHead := \"\"","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/embeddeddolt/version_control.go#L297-L333","documentation":"Merge completed successfully (no merge errors and no conflicts) in a non-read-only store, but the follow-up recompute of the denormalized is_blocked column (recomputeBlockedAfterPull) failed. The merge itself is durable; only the derived blocked-status data may be stale, so the error is reported as a post-merge failure with the original conflicts still returned alongside it.","triggerScenarios":"Calling Merge (used by Sync) on an EmbeddedDoltStore where the merge succeeds but recomputeBlockedAfterPull errors — e.g. the recompute SQL fails, the pinned connection is lost mid-recompute, or the context is cancelled between merge and recompute.","commonSituations":"Network-triggered sync where the context times out right after the merge; database contention during the recompute query; schema/driver errors in the denormalized is_blocked update after schema drift or partial migration.","solutions":["Read the wrapped rerr for the recompute failure cause (SQL error, context cancelled, connection loss) and address that root cause.","Re-run the recompute to refresh is_blocked — rerunning Sync/merge (which will be a no-op merge) or a dedicated recompute path restores consistency.","If the context was cancelled, retry with a longer deadline so merge and recompute complete together.","Check for schema drift on the issues table (is_blocked column) and apply any pending migrations before retrying.","Verify data consistency manually if is_blocked values look stale: the merge succeeded, only derived columns may be outdated."],"exampleFix":"// before: ignoring post-merge recompute need\nconflicts, err := store.Merge(ctx, branch, author)\n\n// after: recover from recompute failure explicitly\nconflicts, err := store.Merge(ctx, branch, author)\nif err != nil && strings.Contains(err.Error(), \"is_blocked recompute failed\") {\n    // merge landed; re-run to refresh derived state\n    _, err = store.Sync(ctx)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"conflicts, err := store.Merge(ctx, branch, author)\nif err != nil {\n    if strings.Contains(err.Error(), \"is_blocked recompute failed\") {\n        // merge landed durably; only derived state is stale.\n        // process conflicts normally, then trigger a recompute/sync.\n        _ = refreshBlockedStatus(ctx)\n    }\n    return conflicts, err\n}","preventionTips":["Use context budgets that cover the merge AND the post-merge recompute.","Keep the issues-table schema (is_blocked column) current with migrations.","Avoid concurrent writes during merge/sync operations.","After this error, re-run sync or a recompute to refresh is_blocked before trusting blocked-status queries."],"tags":["embeddeddolt","merge","is-blocked","denormalized-data"],"backgroundTag":"is-blocked-recompute-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}