gastownhall/beads · error

wake expired defers: iterate %s: %w

Error message

wake expired defers: iterate %s: %w

What it means

Thrown when rows.Err() reports that the result-set iteration itself failed (a mid-stream driver/connection error, not a per-row decode problem). This is distinct from scan errors: the connection dropped or the server aborted the query while streaming ids. The cursor is closed before returning.

Source

Thrown at internal/storage/issueops/wake_defers.go:99

		SELECT id FROM %s
		WHERE status = 'deferred' AND defer_until IS NOT NULL
		  AND defer_until <= UTC_TIMESTAMP()
	`, table))
	if err != nil {
		return nil, fmt.Errorf("wake expired defers: scan %s: %w", table, err)
	}
	var expired []string
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			_ = rows.Close()
			return nil, fmt.Errorf("wake expired defers: scan %s row: %w", table, err)
		}
		expired = append(expired, id)
	}
	if err := rows.Err(); err != nil {
		_ = rows.Close()
		return nil, fmt.Errorf("wake expired defers: iterate %s: %w", table, err)
	}
	if err := rows.Close(); err != nil {
		return nil, fmt.Errorf("wake expired defers: close %s rows: %w", table, err)
	}
	if len(expired) == 0 {
		return nil, nil
	}

	var woken []string
	now := time.Now().UTC()
	for _, id := range expired {
		// row_lock is rewritten so a concurrent claim/update conflicts at
		// commit time instead of cell-merging with this write — the same
		// invariant the lease scheme depends on.
		//nolint:gosec // G201: table is a hardcoded constant from the caller above.
		res, err := tx.ExecContext(ctx, fmt.Sprintf(`
			UPDATE %s
			SET status = 'open', defer_until = NULL, updated_at = ?, row_lock = ?

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the wake operation once the connection is restored — it is idempotent due to the status re-check in the UPDATE.
  2. Check database server logs for aborted connections or killed queries.
  3. Increase driver timeouts (readTimeout/writeTimeout) if large scans routinely fail.
  4. Verify network stability between beads and the database host.

Example fix

// before: default short driver timeout
dsn := "user:pass@tcp(host:3306)/db"
// after
dsn := "user:pass@tcp(host:3306)/db?timeout=30s&readTimeout=60s&writeTimeout=60s"
Defensive patterns

Strategy: retry

Validate before calling

if err := db.PingContext(ctx); err != nil { return fmt.Errorf("connection unhealthy before wake: %w", err) }

Try / catch

var ids []string
err := retry.Do(func() error {
    var innerErr error
    ids, innerErr = issueops.WakeExpiredDefersInTx(ctx, tx, tables)
    return innerErr
}, retry.OnRetry(func(n uint, e error) { log.Printf("wake retry %d: %v", n, e) }), retry.Attempts(3))

Prevention

When it happens

Trigger: Calling WakeExpiredDefersInTx when the MySQL/Dolt connection breaks or the server kills the query partway through streaming the expired-defer id list (timeout, max_allowed_packet issues, server shutdown).

Common situations: Long-running wake scans across a flaky network; Dolt server restart during a maintenance pass; wait_timeout expiring on an idle pooled connection.

Related errors


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