gastownhall/beads · critical

write commit result indeterminate after connection loss (not

Error message

write commit result indeterminate after connection loss (not retried to avoid double-apply): %w

What it means

A write transaction's commit produced the ErrCommitIndeterminate sentinel: the connection was lost at commit time, so it is unknown whether the commit landed server-side. The retry loop deliberately converts this to a permanent error and never replays the callback, because replaying could double-apply the write. Before returning, the failure is recorded via recordDoltPublicationFailure for observability/recovery.

Source

Thrown at internal/storage/dolt/store.go:1158

		if err == nil {
			if !circuitWriteManaged(ctx) && s.breaker != nil {
				s.breaker.RecordSuccess()
			}
			return nil
		}
		// Dolt's exact 1105 autocommit rollback proves the transaction did not
		// land. This is the only 1105 replayed, and withRetryTx is the boundary
		// that recreates the complete SQL transaction on every attempt.
		if isDoltAutocommitRollbackError(err) {
			doltMetrics.serializationErrors.Add(ctx, 1)
			doltMetrics.writeRetries.Add(ctx, 1, metric.WithAttributes(attribute.String("type", "serialization")))
			return err
		}
		// A commit result marked indeterminate may have landed before its
		// response was lost. Never replay the callback in that case.
		if errors.Is(err, ErrCommitIndeterminate) {
			err = s.recordDoltPublicationFailure(ctx, err)
			return backoff.Permanent(fmt.Errorf("write commit result indeterminate after connection loss (not retried to avoid double-apply): %w", err))
		}
		// Serialization failures (1213/1205) guarantee a server-side rollback,
		// so the write never landed — safe to replay at any phase.
		if isSerializationError(err) {
			doltMetrics.serializationErrors.Add(ctx, 1)
			doltMetrics.writeRetries.Add(ctx, 1, metric.WithAttributes(attribute.String("type", "serialization")))
			return err // retryable
		}
		// Connection failures reaching this branch happened before commit;
		// withWriteTx marks ambiguous commit response loss with the public
		// ErrCommitIndeterminate sentinel above.
		if isRetryableError(err) {
			doltMetrics.writeRetries.Add(ctx, 1, metric.WithAttributes(attribute.String("type", "connection")))
			if s.breaker != nil && isConnectionError(err) {
				s.breaker.RecordFailure()
				if s.breaker.State() == circuitOpen {
					doltMetrics.circuitTrips.Add(ctx, 1)
					return backoff.Permanent(fmt.Errorf("%w (circuit breaker tripped)", err))

View on GitHub (pinned to 71377f2769)

Solutions

  1. Do NOT blindly re-run the write — first inspect the data (or the recorded publication failure) to determine whether the commit actually landed.
  2. Check the publication-failure record created by recordDoltPublicationFailure and reconcile: compare expected vs actual row state before re-applying.
  3. Make the write idempotent (deterministic keys/upserts) so a safe re-apply is possible next time.
  4. Investigate the root cause of the connection loss at commit time (server logs, network stability, timeout configuration).

Example fix

// before: naive retry can double-apply
for {
    if err := applyWrite(ctx); err == nil { break }
}
// after: check whether the write landed before re-applying
err := applyWrite(ctx)
if errors.Is(err, storage.ErrCommitIndeterminate) {
    if !writeAlreadyApplied(tx) { // verify server-side state first
        _ = applyWrite(ctx)
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// keep writes idempotent so indeterminate outcomes are reconcilable
// use deterministic primary keys / upserts instead of blind inserts
const upsert = "INSERT INTO issues (id, title) VALUES (?, ?) ON DUPLICATE KEY UPDATE title = VALUES(title)"

Type guard

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

Try / catch

err := store.Update(ctx, op)
if errors.Is(err, storage.ErrCommitIndeterminate) {
    // check server-side state before ever re-applying
    if !op.AlreadyApplied(ctx, store) {
        err = store.Update(ctx, op)
    }
}

Prevention

When it happens

Trigger: tx.Commit() inside withRetryTx/withWriteTx returned a connection-class error (connection lost during commit response), the code classified it as indeterminate, and the retry wrapper stopped all retries and returned this message with the recorded failure wrapped inside.

Common situations: Dolt sql-server restarted exactly at commit time; network partition during a write; client read/write timeout firing while the server was still committing; proxy or load balancer dropping the connection mid-response.

Related errors


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