{"record":{"id":"a20df5c182e8c6e6","repo":"gastownhall/beads","slug":"wake-expired-defers-scan-s-w","errorCode":null,"errorMessage":"wake expired defers: scan %s: %w","messagePattern":"wake expired defers: scan (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/wake_defers.go","lineNumber":86,"sourceCode":"\t\treturn result, err\n\t}\n\tresult.Wisps = wisps\n\treturn result, nil\n}\n\nfunc wakeExpiredDefersInTable(ctx context.Context, tx DBTX, table, eventsTable string) ([]string, error) {\n\t// Snapshot first so each genuinely-woken row gets its own event. The\n\t// UPDATE below repeats the whole predicate, so a row rescued between the\n\t// SELECT and its UPDATE (re-deferred further out, claimed, closed) matches\n\t// nothing and is skipped rather than clobbered.\n\t//nolint:gosec // G201: table is a hardcoded constant from the caller above.\n\trows, err := tx.QueryContext(ctx, fmt.Sprintf(`\n\t\tSELECT id FROM %s\n\t\tWHERE status = 'deferred' AND defer_until IS NOT NULL\n\t\t  AND defer_until <= UTC_TIMESTAMP()\n\t`, table))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"wake expired defers: scan %s: %w\", table, err)\n\t}\n\tvar expired []string\n\tfor rows.Next() {\n\t\tvar id string\n\t\tif err := rows.Scan(&id); err != nil {\n\t\t\t_ = rows.Close()\n\t\t\treturn nil, fmt.Errorf(\"wake expired defers: scan %s row: %w\", table, err)\n\t\t}\n\t\texpired = append(expired, id)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\t_ = rows.Close()\n\t\treturn nil, fmt.Errorf(\"wake expired defers: iterate %s: %w\", table, err)\n\t}\n\tif err := rows.Close(); err != nil {\n\t\treturn nil, fmt.Errorf(\"wake expired defers: close %s rows: %w\", table, err)\n\t}\n\tif len(expired) == 0 {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/wake_defers.go#L68-L104","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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()."],"exampleFix":"// before: schema missing defer_until\nCREATE TABLE issues (id VARCHAR(255), status VARCHAR(32));\n// after\nCREATE TABLE issues (id VARCHAR(255), status VARCHAR(32), defer_until DATETIME NULL, updated_at DATETIME, row_lock VARCHAR(64));","handlingStrategy":"try-catch","validationCode":"// before calling wake maintenance\nif err := db.PingContext(ctx); err != nil { return fmt.Errorf(\"db unreachable: %w\", err) }\n// verify schema\nvar col string\nerr := db.QueryRow(\"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME='issues' AND COLUMN_NAME='defer_until'\").Scan(&col)\nif err != nil { return errors.New(\"run beads migration: defer_until column missing\") }","typeGuard":null,"tryCatchPattern":"ids, err := issueops.WakeExpiredDefersInTx(ctx, tx, tables)\nif err != nil {\n    var driverErr *mysql.MySQLError\n    if errors.As(err, &driverErr) {\n        log.Printf(\"wake failed, driver err %d: %v; check schema/connection\", driverErr.Number, driverErr)\n    }\n    return err // transaction aborts; wake is safe to retry\n}","preventionTips":["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."],"tags":["database","query-failed","deferred-issues"],"backgroundTag":"sql-query-execution-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}