{"record":{"id":"ac58a63b87374c3b","repo":"gastownhall/beads","slug":"sql-commit-ignored-regular-already-committed","errorCode":null,"errorMessage":"sql commit (ignored, regular already committed): %w: %w","messagePattern":"sql commit \\(ignored, regular already committed\\): %w: %w","errorType":"exception","errorClass":"ErrCommitIndeterminate","httpStatus":null,"severity":"critical","filePath":"internal/storage/dolt/transaction.go","lineNumber":280,"sourceCode":"\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\n// beginIgnoredTxOnBranch starts the ignored-tables transaction, checked out to\n// the regular transaction's branch. It borrows a second warm connection from the\n// main pool when one is safely available — the hosted-gateway churn fix: once the\n// pool is warm this costs zero new MySQL handshakes and zero Dolt session-setup\n// round-trips per write. It falls back to a dedicated single-connection pool when\n// borrowing could deadlock (MaxOpenConns==1, the documented case that every\n// branch-isolated test exercises) or when the pool is exhausted or a borrowed","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/transaction.go#L262-L298","documentation":"After the regular SQL transaction and Dolt commit succeed, finishDoltTransaction commits the ignored-tables transaction (wisps, local_metadata, repo_mtimes, events journal when not journal-pinned). If that second COMMIT fails, the regular mutation is already durable and committed to Dolt — so the failure is again flagged with ErrCommitIndeterminate: some effects landed, the ignored-side effects (e.g. wisp rows) did not, and a naive replay could double-apply the regular side.","triggerScenarios":"The ignoredTx COMMIT fails after the regular side fully committed: connection death on the borrowed/fresh ignored connection, ctx canceled during commit, server rejecting the commit (lock conflict, shutdown), or the ignored session's branch was disturbed.","commonSituations":"Borrowed pool connection going stale between BEGIN and COMMIT; server restart mid-operation; mixed durable+wisp writes hitting contention on shared rows when journal mode is off; deadline too short for two-connection commit windows.","solutions":["Verify which effects landed: read back the regular rows and the wisp/ignored rows before deciding to redo anything","Do not blind-replay; route through the library's ErrCommitIndeterminate-aware retry so the connection loss is recorded without replay","Enable the events journal (journal-pinned single-transaction mode) to eliminate the second commit window entirely","Check server health/network; if lock conflicts recur, reduce concurrent writers on the same rows"],"exampleFix":"// before\n// journal off: regular + ignored commit windows can diverge\nstore.RunInTransaction(ctx, msg, fn) // fn mixes durable + wisp writes\n// after\n// journal-pinned mode shares one SQL tx for both planes\nstore.SetEventsJournalEnabled(true)","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isIgnoredCommitIndeterminate(err error) bool { return errors.Is(err, dolt.ErrCommitIndeterminate) }","tryCatchPattern":"if err != nil && errors.Is(err, dolt.ErrCommitIndeterminate) {\n    // regular side is durable; check wisp/ignored rows before redoing anything\n    return verifyAndRepair(ctx, ids)\n}","preventionTips":["Enable events-journal (journal-pinned) mode to collapse the two commits into one","Avoid mixing many durable + wisp writes under journal-off mode in hot paths","Keep borrowed/fresh ignored connections alive (ConnMaxLifetime < server idle timeout)","Reconcile by reading back both planes' rows after any indeterminate error"],"tags":["go","dolt","commit","indeterminate-state","ignored-tables"],"backgroundTag":"commit-outcome-indeterminate","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}