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

  1. Treat the operation as possibly-committed: check durable state (read back rows / `dolt log`) before retrying
  2. 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
  3. Inspect `dolt status` / `dolt log` on the affected branch to confirm whether the revision landed
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/cc0de8a5e7d73be5. Report an issue: GitHub.