gastownhall/beads · critical · ErrCommitIndeterminate
stage and commit after regular SQL commit: %w: %w
Error message
stage and commit after regular SQL commit: %w: %w
What it means
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.
Source
Thrown at internal/storage/dolt/transaction.go:273
// transaction succeeds, later failures have an indeterminate durable outcome.
// When the journal pinned both planes into the regular transaction, that single
// commit already carried the ignored tables and there is no second transaction
// to roll back or commit.
func (s *DoltStore) finishDoltTransaction(ctx context.Context, conn *sql.Conn, tx *doltTransaction, commitMsg string) error {
rollbackIgnored := func() {
if !tx.journalPinned {
_ = tx.ignoredTx.Rollback()
}
}
if err := tx.regularTx.Commit(); err != nil {
rollbackIgnored()
return wrapSQLCommitError("sql commit (regular)", err)
}
if err := versioncontrolops.StageAndCommit(ctx, conn, tx.dirty.DirtyTables(), commitMsg, s.commitAuthorString()); err != nil {
rollbackIgnored()
return fmt.Errorf("stage and commit after regular SQL commit: %w: %w", err, ErrCommitIndeterminate)
}
if tx.journalPinned {
return nil
}
if err := tx.ignoredTx.Commit(); err != nil {
return fmt.Errorf("sql commit (ignored, regular already committed): %w: %w", err, ErrCommitIndeterminate)
}
return nil
}
// ignoredTxBorrowTimeout bounds how long a borrow of a second warm connection
// from the main pool may wait before falling back to a dedicated fresh dial. It
// keeps the second acquisition from ever waiting unboundedly while the caller
// already holds the first (regular-tx) connection, which is what makes deadlock
// impossible by construction on the borrow path.
const ignoredTxBorrowTimeout = 250 * time.Millisecond
View on GitHub (pinned to 71377f2769)
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
Example fix
// before
err := store.RunInTransaction(ctx, msg, func(tx storage.Transaction) error { ... })
if err != nil { return store.RunInTransaction(ctx, msg, fn) } // unsafe replay
// after
if err != nil && errors.Is(err, dolt.ErrCommitIndeterminate) {
// verify state or reconcile instead of replaying
return reconcilePartialCommit(ctx)
} Defensive patterns
Strategy: try-catch
Type guard
func isCommitIndeterminate(err error) bool { return errors.Is(err, dolt.ErrCommitIndeterminate) } Try / catch
if err := store.RunInTransaction(ctx, msg, fn); err != nil {
if errors.Is(err, dolt.ErrCommitIndeterminate) {
// SQL commit may have landed: inspect state / dolt log, do NOT blind-replay
return reconcile(ctx)
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to commit orphaned dependency removals: %w
- sql commit (ignored, regular already committed): %w: %w
- ErrTransaction
- failed to commit restore: %w
- failed to begin transaction: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cc0de8a5e7d73be5.
Report an issue: GitHub.