{"record":{"id":"2cb8b9f0b82d4568","repo":"gastownhall/beads","slug":"journal-scan-derived-is-blocked-from-s-w","errorCode":null,"errorMessage":"journal: scan derived is_blocked from %s: %w","messagePattern":"journal: scan derived is_blocked from (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal.go","lineNumber":240,"sourceCode":"\t\t\t\tend = len(target.ids)\n\t\t\t}\n\t\t\tinClause, args := buildSQLInClause(target.ids[start:end])\n\t\t\t//nolint:gosec // table is one of the two hardcoded values above.\n\t\t\trows, err := tx.QueryContext(ctx,\n\t\t\t\tfmt.Sprintf(\"SELECT id, is_blocked FROM %s WHERE id IN (%s)\", target.table, inClause),\n\t\t\t\targs...)\n\t\t\tif err != nil {\n\t\t\t\tif optionalBlockedTable(target.table) && isTableNotExistError(err) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treturn nil, fmt.Errorf(\"journal: snapshot derived is_blocked from %s: %w\", target.table, err)\n\t\t\t}\n\t\t\tfor rows.Next() {\n\t\t\t\tvar id string\n\t\t\t\tvar blocked int\n\t\t\t\tif err := rows.Scan(&id, &blocked); err != nil {\n\t\t\t\t\t_ = rows.Close()\n\t\t\t\t\treturn nil, fmt.Errorf(\"journal: scan derived is_blocked from %s: %w\", target.table, err)\n\t\t\t\t}\n\t\t\t\tsnapshot[blockedJournalKey{table: target.table, id: id}] = blocked != 0\n\t\t\t}\n\t\t\tif err := rows.Err(); err != nil {\n\t\t\t\t_ = rows.Close()\n\t\t\t\treturn nil, fmt.Errorf(\"journal: iterate derived is_blocked from %s: %w\", target.table, err)\n\t\t\t}\n\t\t\tif err := rows.Close(); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"journal: close derived is_blocked from %s: %w\", target.table, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn snapshot, nil\n}\n\n// recordBlockedJournalChanges compares the stable post-maintenance state with\n// the captured state and journals only beads whose derived is_blocked value\n// actually changed. The emitted update carries the complete post-mutation","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal.go#L222-L258","documentation":"While iterating the is_blocked snapshot rows, rows.Scan(&id, &blocked) failed; the rowset is closed and the transaction fails. This means a returned row's values could not be scanned into (string, int).","triggerScenarios":"is_blocked column is NULL (Scan into int fails) or has a non-integer type (e.g. TEXT) after schema drift; id column NULL or type mismatch.","commonSituations":"Schema changed is_blocked from NOT NULL to nullable or from INT to another type; manual schema edits; data imported from another tool.","solutions":["Inspect the wrapped error to identify the column","Fix the schema: is_blocked must be NOT NULL integer and id NOT NULL string","Backfill NULL is_blocked values to 0/1","Add a migration if upgrading across versions"],"exampleFix":"// before\nvar blocked int\nif err := rows.Scan(&id, &blocked); err != nil { ... }\n// after\nvar blocked sql.NullInt64\nif err := rows.Scan(&id, &blocked); err != nil { ... }\nblockedVal := blocked.Int != 0","handlingStrategy":"try-catch","validationCode":"// verify column types before journal operations\nrows, _ := db.Query(\"SELECT id, is_blocked FROM deps LIMIT 1\")\nvar id string; var blocked sql.NullInt64\nif rows.Next() && rows.Scan(&id, &blocked) != nil { /* schema drift: is_blocked not int */ }","typeGuard":"null","tryCatchPattern":"snap, err := captureBlockedJournalSnapshot(ctx, tx)\nif err != nil {\n  log.Printf(\"blocked snapshot scan failed: %v\", err)\n  return err // abort transaction; fix schema before retry\n}","preventionTips":["Keep is_blocked NOT NULL INT in all blocked-tracking tables","Backfill NULLs after any schema change","Add a migration test comparing expected column types"],"tags":["sql","scan","journal","is-blocked"],"backgroundTag":"sql-scan-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}