{"record":{"id":"39fabc78082e8ab8","repo":"gastownhall/beads","slug":"scan-event-w","errorCode":null,"errorMessage":"scan event: %w","messagePattern":"scan event: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/events.go","lineNumber":142,"sourceCode":"\tquery := `\n\t\tSELECT id, issue_id, event_type, actor, old_value, new_value, comment, created_at\n\t\tFROM events\n\t\tWHERE created_at >= ? AND ((created_at > ?) OR (id > ?))`\n\tif issueID != \"\" {\n\t\tquery += \" AND issue_id = ?\"\n\t}\n\tquery += fmt.Sprintf(\" ORDER BY created_at ASC, id ASC LIMIT %d\", limit)\n\treturn query\n}\n\nfunc scanEvents(rows *sql.Rows) ([]*types.Event, error) {\n\tvar events []*types.Event\n\tfor rows.Next() {\n\t\tvar event types.Event\n\t\tvar oldValue, newValue, comment sql.NullString\n\t\tif err := rows.Scan(&event.ID, &event.IssueID, &event.EventType, &event.Actor,\n\t\t\t&oldValue, &newValue, &comment, &event.CreatedAt); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scan event: %w\", err)\n\t\t}\n\t\tif oldValue.Valid {\n\t\t\tevent.OldValue = &oldValue.String\n\t\t}\n\t\tif newValue.Valid {\n\t\t\tevent.NewValue = &newValue.String\n\t\t}\n\t\tif comment.Valid {\n\t\t\tevent.Comment = &comment.String\n\t\t}\n\t\tevents = append(events, &event)\n\t}\n\treturn events, rows.Err()\n}\n","sourceCodeStart":124,"sourceCodeEnd":157,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/events.go#L124-L157","documentation":"scanEvents iterates result rows from an events query and scans each row into a types.Event. This error wraps rows.Scan failing for a particular row — usually a column count or type mismatch between the SELECT and the scan targets (e.g. NULL handling or changed column order/type). The whole event listing fails rather than returning partial data.","triggerScenarios":"Any events query returning rows whose shape does not match Scan(&ID, &IssueID, &EventType, &Actor, oldValue, newValue, comment, &CreatedAt): schema drift, a driver that returns unexpected types for nullable columns, or corrupted rows.","commonSituations":"Database migrated between versions so the events table gained/dropped columns; custom Dolt schema edits; driver type mapping changes after a driver upgrade.","solutions":["Compare the events table schema against the SELECT column list in the issueops events query and align them.","Run the library's schema migration to restore the expected events table shape.","Check for NULL/empty strings in old_value/new_value/comment columns; they are scanned via sql.NullString so true type mismatches are the culprit.","Inspect the wrapped error for the exact column index that failed."],"exampleFix":"// before\nvar oldValue string\nrows.Scan(&event.ID, &oldValue) // fails on NULL\n// after\nvar oldValue sql.NullString\nrows.Scan(&event.ID, &oldValue)","handlingStrategy":"try-catch","validationCode":"// Verify table shape before scanning:\nrows, err := tx.QueryContext(ctx, \"SELECT id, issue_id, event_type, actor, old_value, new_value, comment, created_at FROM events LIMIT 1\")\nif err != nil {\n\treturn fmt.Errorf(\"events table schema mismatch: %w\", err)\n}\nrows.Close()","typeGuard":null,"tryCatchPattern":"events, err := scanEvents(rows)\nif err != nil {\n\treturn fmt.Errorf(\"reading event rows failed (schema drift?): %w\", err)\n}","preventionTips":["Run schema migration after every library upgrade before issuing queries.","Avoid manual edits to the events table schema (no added/dropped/reordered columns).","Keep nullable columns (old_value, new_value, comment) typed as NULL-able strings so sql.NullString scans succeed."],"tags":["database","scan","schema-mismatch","events"],"backgroundTag":"sql-scan-type-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}