{"record":{"id":"828a6563a8ef56c0","repo":"gastownhall/beads","slug":"scanning-dependencies-row-for-migration-0053-id-ba","errorCode":null,"errorMessage":"scanning dependencies row for migration 0053 id backfill: %w","messagePattern":"scanning dependencies row for migration 0053 id backfill: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/migration_repairs.go","lineNumber":471,"sourceCode":"func backfillDependenciesID(ctx context.Context, db DBConn) error {\n\trows, err := db.QueryContext(ctx, `\n\t\tSELECT issue_id, depends_on_issue_id, depends_on_wisp_id, depends_on_external\n\t\tFROM dependencies\n\t\tWHERE id IS NULL\n\t`)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"reading dependencies rows for migration 0053 id backfill: %w\", err)\n\t}\n\ttype edge struct {\n\t\tissueID                                              string\n\t\tdependsOnIssueID, dependsOnWispID, dependsOnExternal sql.NullString\n\t}\n\tvar edges []edge\n\tfor rows.Next() {\n\t\tvar e edge\n\t\tif err := rows.Scan(&e.issueID, &e.dependsOnIssueID, &e.dependsOnWispID, &e.dependsOnExternal); err != nil {\n\t\t\t_ = rows.Close()\n\t\t\treturn fmt.Errorf(\"scanning dependencies row for migration 0053 id backfill: %w\", err)\n\t\t}\n\t\tedges = append(edges, e)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn fmt.Errorf(\"iterating dependencies rows for migration 0053 id backfill: %w\", err)\n\t}\n\t_ = rows.Close()\n\n\tfor _, e := range edges {\n\t\ttarget := firstNonNullString(e.dependsOnIssueID, e.dependsOnWispID, e.dependsOnExternal)\n\t\tif target == \"\" {\n\t\t\t// ck_dep_one_target (0041) should make a targetless row\n\t\t\t// unreachable; if one exists anyway, leave its id NULL here --\n\t\t\t// ensureDependenciesIDPrimaryKey below checks for exactly this\n\t\t\t// and fails loudly with an actionable count instead of letting a\n\t\t\t// blind MODIFY ... NOT NULL hard-fail on it, or silently keying\n\t\t\t// the table while pretending the row doesn't exist.\n\t\t\tcontinue","sourceCodeStart":453,"sourceCodeEnd":489,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/migration_repairs.go#L453-L489","documentation":"This wraps a rows.Scan failure while reading dependencies rows whose id is NULL for deterministic backfill during the migration-0053 repair. Scanning fails when the actual column types/nullability in the database do not match the scan destinations (string + three sql.NullString). It indicates schema drift or corrupted row data rather than a query failure.","triggerScenarios":"backfillDependenciesID executes `SELECT issue_id, depends_on_issue_id, depends_on_wisp_id, depends_on_external FROM dependencies WHERE id IS NULL` and rows.Scan errors on a row — e.g. issue_id NULL when the scan target is a plain string, or a driver type mismatch (bytes vs string) from an unexpected column type.","commonSituations":"A partially-applied or hand-edited migration left issue_id nullable with NULL rows; a driver/charset change returning []byte where string is expected; rows inserted by an older schema version with different types.","solutions":["Find the offending row: run the same WHERE id IS NULL query manually and inspect NULLs/types","Fix data: backfill or delete rows where issue_id IS NULL before re-running the repair","Align column types with expectations (issue_id NOT NULL VARCHAR/CHAR)","If the driver returns []byte, ensure DSN settings (e.g. interpolateParams/charset) or scan into sql.RawBytes/[]byte"],"exampleFix":"// before: string target rejects NULL issue_id\nvar e edge\nrows.Scan(&e.issueID, ...)\n// after: tolerate NULLs at the source\nvar issueID sql.NullString\nrows.Scan(&issueID, &e.dependsOnIssueID, &e.dependsOnWispID, &e.dependsOnExternal)\nif !issueID.Valid { skip or repair row }","handlingStrategy":"validation","validationCode":"// detect NULL or mistyped issue_id rows before running the repair\nrows, err := db.QueryContext(ctx, `\n  SELECT COUNT(*) FROM dependencies\n  WHERE id IS NULL AND (issue_id IS NULL\n     OR depends_on_issue_id IS NULL AND depends_on_wisp_id IS NULL AND depends_on_external IS NULL)`)\nif err != nil { return err }\nvar bad int\nrows.Scan(&bad)\nif bad > 0 {\n    return fmt.Errorf(\"%d dependencies rows need manual repair before id backfill\", bad)\n}","typeGuard":null,"tryCatchPattern":"if err := repairV53RigAndSplitTargets(ctx, db); err != nil {\n    if strings.Contains(err.Error(), \"scanning dependencies row\") {\n        // inspect offending rows: NULL issue_id or type drift\n        return fmt.Errorf(\"fix dependencies data (NULL issue_id / wrong column types) then retry: %w\", err)\n    }\n    return err\n}","preventionTips":["Enforce NOT NULL on dependencies.issue_id in schema","Scan nullable columns into sql.NullString everywhere in repair code","Audit rows with id IS NULL before each repair run","Avoid hand-editing migration-applied tables"],"tags":["database","backfill","mysql","data-corruption","schema-migration"],"backgroundTag":"row-scan-type-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}