gastownhall/beads · critical · storage.ErrCommitIndeterminate

%s: %w: %w

Error message

%s: %w: %w

What it means

wrapCommitIndeterminate marks a Dolt transaction commit whose outcome could not be determined: the commit call failed, but the library cannot tell whether the data actually landed. The returned error chains the operation name, the underlying driver error, and storage.ErrCommitIndeterminate so callers can detect the ambiguous-outcome family with errors.Is. It is raised from commitEmbeddedTx and the commitAllInTx / runTransactionWithMessage / stageAndCommitAfterSQLCommit paths whenever a DOLT_COMMIT (or transaction flush) fails in a way that is not a clean, provable rollback.

Source

Thrown at internal/storage/embeddeddolt/transaction.go:66

		commitMsg, err = fn(&embeddedTransaction{tx: tx, dirty: &tracker})
		return err
	}); err != nil {
		return err
	}

	// Create a Dolt version commit from the working set changes.
	if commitMsg != "" && len(tracker.DirtyTables()) > 0 {
		if err := s.withMutatingDBConn(ctx, func(db versioncontrolops.DBConn) error {
			return versioncontrolops.StageAndCommit(ctx, db, tracker.DirtyTables(), commitMsg, commitAuthor)
		}); err != nil {
			return wrapCommitIndeterminate("embeddeddolt: stage and commit after SQL commit", err)
		}
	}
	return nil
}

func wrapCommitIndeterminate(op string, err error) error {
	return fmt.Errorf("%s: %w: %w", op, err, storage.ErrCommitIndeterminate)
}

type embeddedTransaction struct {
	tx    *sql.Tx
	dirty *versioncontrolops.DirtyTableTracker
}

func (t *embeddedTransaction) CreateIssue(ctx context.Context, issue *types.Issue, actor string) error {
	bc, err := issueops.NewBatchContext(ctx, t.tx, storage.BatchCreateOptions{SkipPrefixValidation: true})
	if err != nil {
		return err
	}
	result, err := issueops.CreateIssueInTxWithResult(ctx, t.tx, bc, issue, actor)
	if err != nil {
		return err
	}
	for table := range issueops.CreateIssueDirtyTables(ctx, issue, result) {
		t.dirty.MarkDirty(table)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Treat the operation as ambiguous: check the current state (read back the rows / HEAD) before retrying instead of blindly re-applying the write.
  2. Use errors.Is(err, storage.ErrCommitIndeterminate) to branch on this family specifically, then re-run the intended operation idempotently or reconcile.
  3. Inspect the wrapped driver error (%w in the middle) for the root cause — connection, disk, or Dolt internal — and fix that first.
  4. Run `bd doctor` / integrity checks on the embedded database if you suspect a partially applied commit, then retry the command.
  5. If caused by crashes, ensure only one bd process writes at a time and retry the failed command after verifying state.

Example fix

// before: retrying blindly after any commit error
if err := tx.Commit(); err != nil {
    return retry(op)
}

// after: detect the indeterminate family first
if err := doWrite(ctx); err != nil {
    if errors.Is(err, storage.ErrCommitIndeterminate) {
        if exists(op.Key()) {
            return nil // already applied
        }
    }
    return err
}
Defensive patterns

Strategy: try-catch

Type guard

func isCommitIndeterminate(err error) bool {
    return errors.Is(err, storage.ErrCommitIndeterminate)
}

Try / catch

if err := doWrite(ctx); err != nil {
    if errors.Is(err, storage.ErrCommitIndeterminate) {
        // outcome unknown: read back state / HEAD before any retry,
        // make the operation idempotent, do not blindly re-apply
        if alreadyApplied(op.Key()) {
            return nil
        }
    }
    return err
}

Prevention

When it happens

Trigger: A transaction commit inside the embedded store fails with a non-nothing-to-commit error from Dolt (driver/connection failure, crash mid-commit, Dolt internal error) during commitEmbeddedTx, commitAllInTx, stageAndCommitAfterSQLCommit, commitAllInTx via runTransactionWithMessage, or joinTransactionCleanupError aggregation.

Common situations: Process crash or kill during a `bd` write so the transaction outcome on disk is unknown; Dolt storage-layer errors mid-commit; a test (TestDoltCommitResponseLossIsIndeterminate) simulating response loss; concurrent access corrupting transaction state so the commit result is ambiguous.

Related errors


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