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
- Retry the wake operation once the connection is restored — it is idempotent due to the status re-check in the UPDATE.
- Check database server logs for aborted connections or killed queries.
- Increase driver timeouts (readTimeout/writeTimeout) if large scans routinely fail.
- 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
- Set generous readTimeout/writeTimeout in the DSN for long scans.
- Keep connections alive; avoid idle timeouts (wait_timeout) during maintenance.
- Retry wake operations — the guarded UPDATE makes them idempotent.
- Monitor server logs for aborted connections.
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.