gastownhall/beads · warning
wake expired defers: close %s rows: %w
Error message
wake expired defers: close %s rows: %w
What it means
Thrown when rows.Close() returns an error after iteration completed. Close errors indicate the driver could not cleanly release the result set — usually a symptom of an already-broken connection rather than a data problem. The ids collected so far are discarded because the function returns an error.
Source
Thrown at internal/storage/issueops/wake_defers.go:102
`, 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 = ?
WHERE id = ? AND status = 'deferred' AND defer_until IS NOT NULL
AND defer_until <= UTC_TIMESTAMP()
`, table), now, freshRowLock(), id)View on GitHub (pinned to 71377f2769)
Solutions
- Treat the wrapped cause as diagnostic — retry the whole operation on a fresh connection.
- Configure connection pooling with health checks/lifetime limits so stale connections are culled.
- Update the database driver if it spuriously errors on Close for dead connections.
- Check server logs to confirm whether the connection was severed server-side.
Example fix
// before: unlimited connection lifetime // (no MaxLifetime configured) // after db.SetConnMaxLifetime(5 * time.Minute) db.SetConnMaxIdleTime(1 * time.Minute)
Defensive patterns
Strategy: retry
Validate before calling
// cull stale connections before maintenance
stats := db.Stats()
if stats.OpenConnections == 0 { return errors.New("no db connections available") } Try / catch
if err != nil {
log.Printf("rows close failed (likely stale connection): %v — retrying operation", err)
return retryWake(ctx) // full retry on a fresh connection
} Prevention
- Set db.SetConnMaxLifetime shorter than server wait_timeout.
- Enable pool health checks so dead connections are culled.
- Upgrade the driver if it spuriously errors on Close.
- Treat close errors as connection-health signals, not data problems.
When it happens
Trigger: Calling WakeExpiredDefersInTx when the underlying connection becomes unusable between finishing iteration and closing the rows cursor.
Common situations: Connection pool returned a stale connection that died mid-query; server closed the connection after streaming; driver-specific quirks reporting close errors on dead connections.
Related errors
- iter dependents: acquire conn: %w
- checkout active branch %q: %w
- failed to rebuild pool after migration: %w
- schema: pin connection: %w
- acquire connection for gc: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f79397a791066191.
Report an issue: GitHub.