{"record":{"id":"cc0de8a5e7d73be5","repo":"gastownhall/beads","slug":"stage-and-commit-after-regular-sql-commit-w-w","errorCode":null,"errorMessage":"stage and commit after regular SQL commit: %w: %w","messagePattern":"stage and commit after regular SQL commit: %w: %w","errorType":"exception","errorClass":"ErrCommitIndeterminate","httpStatus":null,"severity":"critical","filePath":"internal/storage/dolt/transaction.go","lineNumber":273,"sourceCode":"// transaction succeeds, later failures have an indeterminate durable outcome.\n// When the journal pinned both planes into the regular transaction, that single\n// commit already carried the ignored tables and there is no second transaction\n// to roll back or commit.\nfunc (s *DoltStore) finishDoltTransaction(ctx context.Context, conn *sql.Conn, tx *doltTransaction, commitMsg string) error {\n\trollbackIgnored := func() {\n\t\tif !tx.journalPinned {\n\t\t\t_ = tx.ignoredTx.Rollback()\n\t\t}\n\t}\n\n\tif err := tx.regularTx.Commit(); err != nil {\n\t\trollbackIgnored()\n\t\treturn wrapSQLCommitError(\"sql commit (regular)\", err)\n\t}\n\n\tif err := versioncontrolops.StageAndCommit(ctx, conn, tx.dirty.DirtyTables(), commitMsg, s.commitAuthorString()); err != nil {\n\t\trollbackIgnored()\n\t\treturn fmt.Errorf(\"stage and commit after regular SQL commit: %w: %w\", err, ErrCommitIndeterminate)\n\t}\n\n\tif tx.journalPinned {\n\t\treturn nil\n\t}\n\tif err := tx.ignoredTx.Commit(); err != nil {\n\t\treturn fmt.Errorf(\"sql commit (ignored, regular already committed): %w: %w\", err, ErrCommitIndeterminate)\n\t}\n\treturn nil\n}\n\n// ignoredTxBorrowTimeout bounds how long a borrow of a second warm connection\n// from the main pool may wait before falling back to a dedicated fresh dial. It\n// keeps the second acquisition from ever waiting unboundedly while the caller\n// already holds the first (regular-tx) connection, which is what makes deadlock\n// impossible by construction on the borrow path.\nconst ignoredTxBorrowTimeout = 250 * time.Millisecond\n","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/transaction.go#L255-L291","documentation":"finishDoltTransaction commits the regular SQL transaction, then calls versioncontrolops.StageAndCommit to stage the dirty tables and create the Dolt commit. If StageAndCommit fails AFTER the regular SQL commit already succeeded, the mutation's SQL state is durable but its Dolt revision is not — an indeterminate outcome. The library therefore wraps the error together with ErrCommitIndeterminate so callers/retry logic know the work may have landed and must NOT blindly replay.","triggerScenarios":"DOLT_COMMIT / dolt_add failing server-side after the SQL COMMIT; connection to the pinned conn lost during StageAndCommit; nothing-to-commit mis-detection aside, a Dolt storage or conflict error during commit; ctx canceled between SQL commit and Dolt commit.","commonSituations":"Server restart or crash in the tiny window between SQL COMMIT and DOLT_COMMIT; long callbacks letting the session idle out before commit; concurrent writers racing on the same branch; disk-full or .dolt storage corruption on embedded mode.","solutions":["Treat the operation as possibly-committed: check durable state (read back rows / `dolt log`) before retrying","Use the recorded retry path — runInTransaction propagates ErrCommitIndeterminate to withRetry so the lost connection is recorded without replay; do not wrap this call in your own blind retry loop","Inspect `dolt status` / `dolt log` on the affected branch to confirm whether the revision landed","Fix the underlying cause (network stability, server health, disk space) before the next write"],"exampleFix":"// before\nerr := store.RunInTransaction(ctx, msg, func(tx storage.Transaction) error { ... })\nif err != nil { return store.RunInTransaction(ctx, msg, fn) } // unsafe replay\n// after\nif err != nil && errors.Is(err, dolt.ErrCommitIndeterminate) {\n    // verify state or reconcile instead of replaying\n    return reconcilePartialCommit(ctx)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isCommitIndeterminate(err error) bool { return errors.Is(err, dolt.ErrCommitIndeterminate) }","tryCatchPattern":"if err := store.RunInTransaction(ctx, msg, fn); err != nil {\n    if errors.Is(err, dolt.ErrCommitIndeterminate) {\n        // SQL commit may have landed: inspect state / dolt log, do NOT blind-replay\n        return reconcile(ctx)\n    }\n    return err\n}","preventionTips":["Never wrap RunInTransaction in an unconditional retry loop","After ErrCommitIndeterminate, verify durable state before re-applying","Keep the SQL-commit-to-DOLT_COMMIT window short (short callbacks, healthy sessions)","Monitor disk space and server health on embedded Dolt hosts"],"tags":["go","dolt","commit","indeterminate-state","transaction"],"backgroundTag":"commit-outcome-indeterminate","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}