{"record":{"id":"036320ed8f780edb","repo":"gastownhall/beads","slug":"failed-to-scan-history-w-036320","errorCode":null,"errorMessage":"failed to scan history: %w","messagePattern":"failed to scan history: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/history.go","lineNumber":60,"sourceCode":"\tvar entries []*storage.HistoryEntry\n\tfor rows.Next() {\n\t\tvar issue types.Issue\n\t\tvar createdAtStr, updatedAtStr sql.NullString\n\t\tvar closedAt sql.NullTime\n\t\tvar assignee, owner, createdBy, closeReason, molType sql.NullString\n\t\tvar estimatedMinutes sql.NullInt64\n\t\tvar pinned sql.NullInt64\n\t\tvar commitHash, committer string\n\t\tvar commitDate time.Time\n\n\t\tif err := rows.Scan(\n\t\t\t&issue.ID, &issue.Title, &issue.Description, &issue.Design, &issue.AcceptanceCriteria, &issue.Notes,\n\t\t\t&issue.Status, &issue.Priority, &issue.IssueType, &assignee, &owner, &createdBy,\n\t\t\t&estimatedMinutes, &createdAtStr, &updatedAtStr, &closedAt, &closeReason,\n\t\t\t&pinned, &molType,\n\t\t\t&commitHash, &committer, &commitDate,\n\t\t); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to scan history: %w\", err)\n\t\t}\n\n\t\tif createdAtStr.Valid {\n\t\t\tissue.CreatedAt = ParseTimeString(createdAtStr.String)\n\t\t}\n\t\tif updatedAtStr.Valid {\n\t\t\tissue.UpdatedAt = ParseTimeString(updatedAtStr.String)\n\t\t}\n\t\tif closedAt.Valid {\n\t\t\tissue.ClosedAt = &closedAt.Time\n\t\t}\n\t\tif assignee.Valid {\n\t\t\tissue.Assignee = assignee.String\n\t\t}\n\t\tif owner.Valid {\n\t\t\tissue.Owner = owner.String\n\t\t}\n\t\tif createdBy.Valid {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/history.go#L42-L78","documentation":"HistoryInTx wraps any error from row.Scan while reading history rows into an Issue struct. The scan fails when a column in the SELECT does not match the destination Go types (NULL into non-nullable fields, string/date conversion failures, wrong column count). The %w wrapping preserves the underlying driver error for errors.Is/As inspection.","triggerScenarios":"A column expected non-NULL is NULL in a history row (e.g. created_by, issue_type, commit_hash) and Scan into a string/[]byte destination rejects it; or the schema evolved (column added/renamed/removed) so the SELECT column list no longer matches the Scan destination list.","commonSituations":"Dolt schema migration leaving older rows with NULLs in columns added later; hand-edited or imported rows missing required fields; running a new binary against an older database missing columns.","solutions":["Check the underlying driver error (errors.Is / %v of the wrapped cause) to identify the offending column or conversion","Find the history row with the NULL value and backfill it (UPDATE ... SET col = default WHERE col IS NULL)","Ensure the database schema matches the version of the code (run migrations)","Add sql.Null* destinations or COALESCE in the SELECT for columns that may legitimately be NULL"],"exampleFix":"// before\n&createdBy,\n// after\nvar createdBy sql.NullString\n... scan into &createdBy, then:\nif createdBy.Valid { issue.CreatedBy = createdBy.String }","handlingStrategy":"try-catch","validationCode":"// check for NULLs in history rows before scanning\nrows, _ := db.Query(\"SELECT id, created_by, issue_type FROM issues WHERE id = ?\", id)\nfor rows.Next() {\n  var createdBy, issueType sql.NullString\n  rows.Scan(&id, &createdBy, &issueType)\n  if !createdBy.Valid || !issueType.Valid { /* backfill or skip row */ }\n}","typeGuard":"func scanableHistoryRow(createdBy, issueType sql.NullString) bool {\n  return createdBy.Valid && issueType.Valid\n}","tryCatchPattern":"issue, err := issueops.HistoryInTx(ctx, tx, issueID)\nif err != nil {\n  var scanErr *fmt.ScanError // or inspect wrapped cause\n  if errors.Is(err, sql.ErrNoRows) { return nil, storage.ErrNotFound }\n  log.Printf(\"history scan failed: %v\", err) // cause identifies the column\n  return nil, fmt.Errorf(\"history unavailable: %w\", err)\n}","preventionTips":["Use sql.Null* types for all nullable columns","Keep SELECT column lists and Scan destinations in lockstep when editing the query","Run schema migrations before deploying new code","Backfill NOT NULL columns with defaults when adding them"],"tags":["sql","scan","storage","null-value"],"backgroundTag":"sql-scan-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}