{"record":{"id":"6d828c4efa52fd68","repo":"gastownhall/beads","slug":"wake-expired-defers-scan-s-row-w","errorCode":null,"errorMessage":"wake expired defers: scan %s row: %w","messagePattern":"wake expired defers: scan (.+?) row: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/wake_defers.go","lineNumber":93,"sourceCode":"\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 {\n\t\treturn nil, nil\n\t}\n\n\tvar woken []string\n\tnow := time.Now().UTC()\n\tfor _, id := range expired {\n\t\t// row_lock is rewritten so a concurrent claim/update conflicts at","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/wake_defers.go#L75-L111","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before: nullable id allows NULL rows\nid VARCHAR(255) NULL\n// after\nid VARCHAR(255) NOT NULL PRIMARY KEY","handlingStrategy":"validation","validationCode":"rows, err := db.Query(\"SELECT COUNT(*) FROM issues WHERE id IS NULL\")\nif err != nil { return err }\ndefer rows.Close()\nvar n int\nrows.Scan(&n)\nif n > 0 { return fmt.Errorf(\"%d issues rows have NULL id; repair before waking defers\", n) }","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"converting NULL to string\") {\n        return fmt.Errorf(\"corrupt row with NULL id in %s: repair table\", table)\n    }\n    return err\n}","preventionTips":["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."],"tags":["database","row-scan","data-corruption"],"backgroundTag":"sql-row-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}