{"record":{"id":"b960bcd17f9c343f","repo":"gastownhall/beads","slug":"backfilling-dependencies-id-for-migration-0053-w","errorCode":null,"errorMessage":"backfilling dependencies.id for migration 0053: %w","messagePattern":"backfilling dependencies\\.id for migration 0053: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/migration_repairs.go","lineNumber":499,"sourceCode":"\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\n\t\t}\n\t\tid := depid.New(e.issueID, target)\n\t\tif _, err := db.ExecContext(ctx, `\n\t\t\tUPDATE dependencies SET id = ?\n\t\t\tWHERE issue_id = ?\n\t\t\t  AND depends_on_issue_id <=> ?\n\t\t\t  AND depends_on_wisp_id <=> ?\n\t\t\t  AND depends_on_external <=> ?\n\t\t`, id, e.issueID, e.dependsOnIssueID, e.dependsOnWispID, e.dependsOnExternal); err != nil {\n\t\t\treturn fmt.Errorf(\"backfilling dependencies.id for migration 0053: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// 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}","sourceCodeStart":481,"sourceCodeEnd":517,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/migration_repairs.go#L481-L517","documentation":"This wraps the SQL error raised while backfilling the new `dependencies.id` column (migration 0053 repair). The repair assigns each dependency row a deterministic CHAR(36) id derived from its (issue_id, depends_on_issue_id, depends_on_wisp_id, depends_on_external) natural identity via an UPDATE; this error means that UPDATE failed for one of those rows.","triggerScenarios":"Running the migration-0053 repair path (ensureDependenciesIDColumn -> backfillDependenciesID) against a Dolt/MySQL database where the per-row `UPDATE dependencies SET id = ? ...` statement fails — e.g. connection dropped mid-backfill, lock timeout on a concurrently written dependencies table, or the column was added but a statement-level constraint rejects the computed value.","commonSituations":"Upgrading an older beads database whose dependencies table predates 0053; starting `bd` (which runs ensureSchema) while another bd process holds row locks on dependencies; network/daemon interruption to the embedded Dolt server during startup repair.","solutions":["Rerun the command once the database is reachable and no other bd process is running; the backfill is resumable and skips rows already assigned an id","Check database connectivity and Dolt server logs for the underlying %w cause (lock wait timeout, connection refused, etc.)","If locks are the cause, kill lingering bd processes (`ps aux | grep bd`) or stale Dolt transactions, then retry","As a last resort, back up the database and restore, then let migration run on a clean copy"],"exampleFix":"// before: concurrent lock during backfill\nbd ready  // fails: backfilling dependencies.id for migration 0053: lock wait timeout\n// after: ensure no competing process, then retry\npkill -f 'bd ' ; bd ready  // migration resumes and completes","handlingStrategy":"retry","validationCode":"// preflight: ensure single writer and reachable db\nbd dolt sql -q 'SELECT COUNT(*) FROM dependencies WHERE id IS NULL' || echo 'db unreachable or locked'","typeGuard":null,"tryCatchPattern":"// Go caller of the schema-ensure step\nif err := schema.Ensure(ctx, db); err != nil {\n    if strings.Contains(err.Error(), \"backfilling dependencies.id\") && isTransient(err) {\n        time.Sleep(backoff); continue // retry: resumable backfill\n    }\n    return err\n}","preventionTips":["Run only one bd process against a database during upgrades","Upgrade beads during a maintenance window","Check Dolt server health/disk before major migrations","Keep a database backup before version upgrades"],"tags":["database","migration","sql","backfill"],"backgroundTag":"migration-backfill-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}