{"record":{"id":"f2907219d0c888e4","repo":"gastownhall/beads","slug":"migration-0053-d-dependencies-row-s-have-no-dep","errorCode":null,"errorMessage":"migration 0053: %d dependencies row(s) have no depends_on_issue_id/depends_on_wisp_id/depends_on_external target and cannot be assigned an id (ck_dep_one_target should prevent this); repair manually before retrying","messagePattern":"migration 0053: (.+?) dependencies row\\(s\\) have no depends_on_issue_id/depends_on_wisp_id/depends_on_external target and cannot be assigned an id \\(ck_dep_one_target should prevent this\\); repair manually before retrying","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/storage/schema/migration_repairs.go","lineNumber":523,"sourceCode":"// ensureDependenciesIDPrimaryKey finishes restoring dependencies.id to 0043's\n// canonical shape: NOT NULL and the table's PRIMARY KEY. It re-verifies both\n// independently of whether this pass just backfilled anything, so a re-entry\n// after a crash between the backfill and the key (or between MODIFY NOT NULL\n// and ADD PRIMARY KEY) finishes the remaining step(s) instead of re-running\n// ones already done -- MODIFY COLUMN restating an identical definition and\n// re-adding an already-present PRIMARY KEY are otherwise either redundant or\n// outright rejected as a duplicate key.\nfunc ensureDependenciesIDPrimaryKey(ctx context.Context, db DBConn) error {\n\tvar remainingNull int\n\tif err := db.QueryRowContext(ctx, \"SELECT COUNT(*) FROM dependencies WHERE id IS NULL\").Scan(&remainingNull); err != nil {\n\t\treturn fmt.Errorf(\"counting unbackfilled dependencies.id rows for migration 0053: %w\", err)\n\t}\n\tif remainingNull > 0 {\n\t\t// Fail with an actionable count now rather than let a subsequent\n\t\t// MODIFY COLUMN ... NOT NULL below abort with a generic \"column\n\t\t// cannot be null\" error, or silently key the table while leaving\n\t\t// NULL-id rows behind it.\n\t\treturn fmt.Errorf(\"migration 0053: %d dependencies row(s) have no depends_on_issue_id/depends_on_wisp_id/depends_on_external target and cannot be assigned an id (ck_dep_one_target should prevent this); repair manually before retrying\", remainingNull)\n\t}\n\n\tidIsPrimaryKey, err := schemaColumnInPrimaryKey(ctx, db, \"dependencies\", \"id\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"checking dependencies.id primary key: %w\", err)\n\t}\n\tif idIsPrimaryKey {\n\t\treturn nil\n\t}\n\n\tif _, err := db.ExecContext(ctx, \"ALTER TABLE dependencies MODIFY COLUMN id CHAR(36) NOT NULL\"); err != nil {\n\t\treturn fmt.Errorf(\"finalizing dependencies.id for migration 0053: %w\", err)\n\t}\n\n\thasAnyPrimaryKey, err := schemaHasPrimaryKey(ctx, db, \"dependencies\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"checking dependencies for an existing primary key: %w\", err)\n\t}","sourceCodeStart":505,"sourceCodeEnd":541,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/migration_repairs.go#L505-L541","documentation":"After backfill, some `dependencies` rows still have NULL id because all three target columns (depends_on_issue_id, depends_on_wisp_id, depends_on_external) are NULL — there is nothing to derive an id from. The `ck_dep_one_target` check constraint should make this impossible, so these rows are drifted data. The repair aborts with the row count instead of keying the table with NULL ids.","triggerScenarios":"ensureDependenciesIDPrimaryKey finds remainingNull > 0 during the 0053 repair — typically a pre-constraint database (written before ck_dep_one_target was added) or rows inserted by a buggy/old version or manual SQL bypassing constraints.","commonSituations":"Databases migrated across several beads versions where ck_dep_one_target never ran; rows hand-inserted with `bd dolt sql` omitting target columns; a failed partial import/merge leaving orphan dependency rows.","solutions":["Find the offending rows: SELECT * FROM dependencies WHERE id IS NULL","Delete genuinely orphaned rows or set one of depends_on_issue_id / depends_on_wisp_id / depends_on_external so exactly one target is present","Verify ck_dep_one_target exists on the table and re-add it if missing","Rerun `bd` — the repair recounts and proceeds when remainingNull == 0"],"exampleFix":"// before: drifted rows block migration\n// after: repair or remove rows, then retry\nbd dolt sql -q \"DELETE FROM dependencies WHERE id IS NULL AND depends_on_issue_id IS NULL AND depends_on_wisp_id IS NULL AND depends_on_external IS NULL\" ; bd ready","handlingStrategy":"validation","validationCode":"-- run before upgrading; must return zero rows\nSELECT issue_id, depends_on_issue_id, depends_on_wisp_id, depends_on_external\nFROM dependencies\nWHERE depends_on_issue_id IS NULL\n  AND depends_on_wisp_id IS NULL\n  AND depends_on_external IS NULL;","typeGuard":null,"tryCatchPattern":"if err := ensureSchema(ctx, db); err != nil {\n    var n int\n    if _, scanErr := fmt.Sscanf(err.Error(), \"migration 0053: %d dependencies row(s)\", &n); scanErr == nil {\n        return fmt.Errorf(\"run repair SQL for %d orphan dependency rows, then retry\", n)\n    }\n    return err\n}","preventionTips":["Never insert dependencies rows via raw SQL bypassing the app layer","Keep ck_dep_one_target in place; re-add after manual schema edits","Upgrade through each intermediate beads version rather than skipping schema eras","Audit orphan rows after imports/merges"],"tags":["database","migration","data-integrity","constraint"],"backgroundTag":"migration-backfill-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}