gastownhall/beads · error

failed to read active branch: %w

Error message

failed to read active branch: %w

What it means

After acquiring a pooled connection, runDoltTransaction runs `SELECT active_branch()` to learn which Dolt branch the session is on, so the ignored-tables transaction can be pinned to the same branch. This error wraps a failure of that query — the connection is unusable (dead/stale server session), the query was canceled by ctx, or the server is not speaking the Dolt SQL dialect expected here.

Source

Thrown at internal/storage/dolt/transaction.go:191

	// Detect pool-wait: if WaitCount increased, the pool was exhausted and
	// this caller had to wait for a connection to become available.
	if err == nil {
		statsAfter := s.db.Stats()
		if statsAfter.WaitCount > statsBefore.WaitCount {
			doltMetrics.poolWaitCount.Add(ctx, statsAfter.WaitCount-statsBefore.WaitCount)
			waitMs := float64(statsAfter.WaitDuration-statsBefore.WaitDuration) / float64(time.Millisecond)
			doltMetrics.poolWaitMs.Record(ctx, waitMs)
		}
	}

	if err != nil {
		return fmt.Errorf("failed to acquire connection: %w", err)
	}
	defer conn.Close()

	var currentBranch string
	if err := conn.QueryRowContext(ctx, "SELECT active_branch()").Scan(&currentBranch); err != nil {
		return fmt.Errorf("failed to read active branch: %w", err)
	}

	regularTx, err := conn.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("failed to begin regular tx: %w", err)
	}

	// The journal counter and rows must commit in the SAME SQL transaction as
	// every mutation they describe. bd_events_journal and bd_events_seq are
	// dolt_ignored, so on the default split-transaction shape they would land in
	// the ignored transaction while the mutation lands in the regular one: a
	// mixed durable+wisp callback would then make the two transactions contend
	// with each other on the single bd_events_seq row, and the ignored commit
	// can fail AFTER the regular side has already committed — a mutation with no
	// journal record, which is exactly the state the same-transaction guarantee
	// exists to make impossible. In journal mode both planes therefore share the
	// pinned regular transaction. The default journal-off path keeps the
	// established split transactions untouched.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Verify the endpoint is a Dolt SQL server, not plain MySQL (active_branch() is Dolt-specific)
  2. Retry the operation — setup-phase failures before the callback are retried automatically by withTransactionSetupRetry
  3. Check ctx deadlines and network stability between client and Dolt server
  4. Ping/health-check the pool before writes so stale sessions are discarded

Example fix

// before
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
// after
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // allow branch-read round-trip
Defensive patterns

Strategy: retry

Validate before calling

if err := db.PingContext(ctx); err != nil { return fmt.Errorf("stale pool: %w", err) }

Try / catch

if err != nil && errors.Is(err, context.DeadlineExceeded) {
    // pre-callback setup failure: retried automatically; or retry once manually
    return retrySetup(ctx)
}

Prevention

When it happens

Trigger: ctx canceled or deadline exceeded during the round-trip; the pooled connection went stale (server restarted, idle timeout, wait_timeout); connecting to a plain MySQL server instead of a Dolt SQL server where active_branch() does not exist.

Common situations: Dolt server restart between pool warm-up and next use; aggressive proxy/firewall killing idle sessions; accidentally pointing beads at a MySQL-only DSN; extremely short context timeouts.

Related errors


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