gastownhall/beads · error

uow: failed to start transaction: %w

Error message

uow: failed to start transaction: %w

What it means

After pinning a connection, BeginTx executes 'START TRANSACTION;' on it. If that statement fails, the connection is closed and this error wraps the cause as 'uow: failed to start transaction: %w'.

Source

Thrown at internal/storage/uow/dolt_sql_provider.go:152

func (p *doltSQLProvider) Close(ctx context.Context) error {
	if p.db == nil {
		return nil
	}
	db := p.db
	p.db = nil
	return db.Close()
}

func (p *doltSQLProvider) BeginTx(ctx context.Context) (Tx, error) {
	conn, err := p.db.Conn(ctx)
	if err != nil {
		return nil, fmt.Errorf("uow: pin connection: %w", err)
	}

	_, err = conn.ExecContext(ctx, "START TRANSACTION;")
	if err != nil {
		_ = conn.Close()
		return nil, fmt.Errorf("uow: failed to start transaction: %w", err)
	}

	// Bind journal activation to the connection this unit of work is pinned to,
	// AFTER START TRANSACTION so the seq allocation's UPDATE and the SELECT that
	// must observe it are inside one transaction on one session. The scope is
	// released when the connection is (doltServerTx.releaseConn / poisonConn),
	// so an entry cannot outlive its transaction.
	return &doltServerTx{
		conn:              conn,
		clearJournalScope: issueops.ScopeEventsJournalTransaction(conn, p.eventsJournalEnabled.Load()),
	}, nil
}

// selectProbeDatabase lets schema's pre-lock convergence probe reach the
// database on a session that is not yet on one. openAndInitSchema pins its
// schema-init pool with an EMPTY DSN database, so without this the probe reads
// NULL from DATABASE(), declines, and every invocation queues on the
// server-wide migration lock it exists to skip.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the unit of work — pool connections are refreshed automatically after a dropped session.
  2. Increase the context timeout if START TRANSACTION is racing the deadline.
  3. Check Dolt server logs for session kills or restarts at the failure time.
  4. Verify no proxy/idle timeout (defaultProxyIdleTimeout) is reaping connections mid-open.

Example fix

// before
uow, err := provider.NewUOW(ctx) // one short ctx reused everywhere

// after
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
uow, err := provider.NewUOW(ctx)
if err != nil {
    var retryable bool // serialization errors surface similarly
    _ = retryable
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

// health-check the server before starting transactions
if err := db.PingContext(ctx); err != nil {
    return fmt.Errorf("dolt server not ready for transactions: %w", err)
}

Try / catch

tx, err := provider.BeginTx(ctx)
if err != nil {
    if strings.Contains(err.Error(), "failed to start transaction") {
        // connection likely dropped between Conn() and START TRANSACTION — safe to retry once
        return retryOnce(ctx, provider.BeginTx)
    }
    return err
}

Prevention

When it happens

Trigger: conn.ExecContext(ctx, "START TRANSACTION;") returns an error: the server dropped the connection, the context was canceled mid-statement, or the session is in a state that refuses a new transaction (e.g. an implicit transaction already open on that pinned session).

Common situations: Dolt server restart or idle timeout killing the pinned connection between Conn() and START TRANSACTION; network interruption; ctx deadline expiring exactly during transaction start.

Related errors


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