gastownhall/beads · error
wake expired defers: scan %s row: %w
Error message
wake expired defers: scan %s row: %w
What it means
Thrown when rows.Scan fails while reading an id from the expired-defers result set. Scan errors here mean the driver returned a row whose single column could not be decoded into a string (NULL id, driver type mismatch, or rows already invalidated). The rows cursor is closed before returning to avoid leaking the result set.
Source
Thrown at internal/storage/issueops/wake_defers.go:93
// 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 {
return nil, nil
}
var woken []string
now := time.Now().UTC()
for _, id := range expired {
// row_lock is rewritten so a concurrent claim/update conflicts atView on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped driver error and the offending row; fix or delete the row with the malformed id.
- Ensure the id column is NOT NULL with a string-compatible type (VARCHAR/CHAR).
- Update the database driver to a version with correct type-conversion handling.
- Re-run bd doctor / storage integrity checks on the affected table.
Example fix
// before: nullable id allows NULL rows id VARCHAR(255) NULL // after id VARCHAR(255) NOT NULL PRIMARY KEY
Defensive patterns
Strategy: validation
Validate before calling
rows, err := db.Query("SELECT COUNT(*) FROM issues WHERE id IS NULL")
if err != nil { return err }
defer rows.Close()
var n int
rows.Scan(&n)
if n > 0 { return fmt.Errorf("%d issues rows have NULL id; repair before waking defers", n) } Try / catch
if err != nil {
if strings.Contains(err.Error(), "converting NULL to string") {
return fmt.Errorf("corrupt row with NULL id in %s: repair table", table)
}
return err
} Prevention
- Never edit the issues table manually outside beads.
- Keep id columns NOT NULL PRIMARY KEY.
- Run periodic integrity checks (bd doctor).
- Use an up-to-date database driver with correct type conversion.
When it happens
Trigger: A row in the table has a NULL or non-string id value, or the driver returns an incompatible column type for id, during the WakeExpiredDefersInTx scan loop.
Common situations: Corrupted or manually-edited rows where id is NULL; using a driver that returns []byte or custom types without proper conversion support (e.g. Dolt/MySQL drivers with odd column charset declarations).
Related errors
- search %s (id scan): scan: %w
- scan issue ID: %w
- get issue comments: scan: %w
- scan dependency source: %w
- scan dependency target: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6d828c4efa52fd68.
Report an issue: GitHub.