gastownhall/beads · error
wake expired defers: scan %s: %w
Error message
wake expired defers: scan %s: %w
What it means
This error wraps the database driver error returned when the SELECT that finds expired deferred issues in a table fails to execute. wakeExpiredDefersInTable queries `<table>` for rows with status='deferred' and defer_until <= UTC_TIMESTAMP(); any driver/network/SQL error on that query is re-wrapped with the table name for context. The %w wrap preserves the underlying cause for errors.Is/As inspection.
Source
Thrown at internal/storage/issueops/wake_defers.go:86
return result, err
}
result.Wisps = wisps
return result, nil
}
func wakeExpiredDefersInTable(ctx context.Context, tx DBTX, table, eventsTable string) ([]string, error) {
// Snapshot first so each genuinely-woken row gets its own event. The
// UPDATE below repeats the whole predicate, so a row rescued between the
// SELECT and its UPDATE (re-deferred further out, claimed, closed) matches
// nothing and is skipped rather than clobbered.
//nolint:gosec // G201: table is a hardcoded constant from the caller above.
rows, err := tx.QueryContext(ctx, fmt.Sprintf(`
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 {View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped cause (%w) with errors.Is/As to identify the driver-level problem (connection, syntax, unknown column).
- Verify the table schema includes status and defer_until columns; run beads migration/doctor if upgrading.
- Confirm the database is reachable and the transaction's connection is healthy (ping, server logs).
- Ensure the storage driver is MySQL/Dolt-compatible since the query uses UTC_TIMESTAMP().
Example fix
// before: schema missing defer_until CREATE TABLE issues (id VARCHAR(255), status VARCHAR(32)); // after CREATE TABLE issues (id VARCHAR(255), status VARCHAR(32), defer_until DATETIME NULL, updated_at DATETIME, row_lock VARCHAR(64));
Defensive patterns
Strategy: try-catch
Validate before calling
// before calling wake maintenance
if err := db.PingContext(ctx); err != nil { return fmt.Errorf("db unreachable: %w", err) }
// verify schema
var col string
err := db.QueryRow("SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME='issues' AND COLUMN_NAME='defer_until'").Scan(&col)
if err != nil { return errors.New("run beads migration: defer_until column missing") } Try / catch
ids, err := issueops.WakeExpiredDefersInTx(ctx, tx, tables)
if err != nil {
var driverErr *mysql.MySQLError
if errors.As(err, &driverErr) {
log.Printf("wake failed, driver err %d: %v; check schema/connection", driverErr.Number, driverErr)
}
return err // transaction aborts; wake is safe to retry
} Prevention
- Run schema migrations before every version upgrade.
- Monitor database connectivity with health checks before maintenance passes.
- Keep the storage driver MySQL/Dolt-compatible (UTC_TIMESTAMP).
- Wrap wake calls in retry logic — they are idempotent.
When it happens
Trigger: Calling WakeExpiredDefersInTx (directly or via defer-wake maintenance) when the underlying database connection is broken, the transactions/issues table or its defer_until/status columns are missing or renamed, UTC_TIMESTAMP is unavailable (non-MySQL driver), or the query is cancelled via ctx.
Common situations: Dolt/MySQL server restarted or connection dropped mid-scan; running against a schema created by an older beads version lacking defer_until; pointing beads at a SQLite/Postgres backend where UTC_TIMESTAMP() does not exist.
Related errors
- db: GetAllConfig: %w
- db: GetCustomTypes: query custom_types: %w
- get all dependency records from %s: %w
- get dependency records from %s: %w
- get dependency counts (dependents from %s): %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/a20df5c182e8c6e6.
Report an issue: GitHub.