{"record":{"id":"63180e7ae266a1f1","repo":"gastownhall/beads","slug":"checking-dependencies-id-w","errorCode":null,"errorMessage":"checking dependencies.id: %w","messagePattern":"checking dependencies\\.id: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/migration_repairs.go","lineNumber":431,"sourceCode":"// 0053's own \"REPLACE INTO dependencies (id, ...)\" matches rows on the\n// uk_dep_* natural-identity unique keys; an unkeyed id lets a REPLACE that\n// hits a row whose old depends_on_wisp_id is NULL (so uk_dep_wisp_target\n// doesn't match) fall through to INSERT, duplicating the edge under a new id\n// while the stale row survives. Restoring id as the PRIMARY KEY is what makes\n// REPLACE's own conflict detection do its job.\n//\n// This is deliberately re-entrant rather than a single \"column present ->\n// nil\" gate: preMigrationRepair's mutations to a synced table like\n// dependencies land in the same atomic per-step commit as migration 0053\n// (see runMigrations' dirty-table-snapshot ordering), but a process killed\n// mid-repair -- after ADD COLUMN, before the backfill or the key finishes --\n// still needs the NEXT open's repair call to finish the job rather than\n// short-circuit on \"column exists\". Every step below re-verifies its own\n// target state instead of trusting an earlier step ran to completion.\nfunc ensureDependenciesIDColumn(ctx context.Context, db DBConn) error {\n\thasID, err := schemaColumnExists(ctx, db, \"dependencies\", \"id\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"checking dependencies.id: %w\", err)\n\t}\n\tif !hasID {\n\t\tif _, err := db.ExecContext(ctx, \"ALTER TABLE dependencies ADD COLUMN id CHAR(36) NULL\"); err != nil {\n\t\t\treturn fmt.Errorf(\"adding dependencies.id for migration 0053: %w\", err)\n\t\t}\n\t}\n\n\tif err := backfillDependenciesID(ctx, db); err != nil {\n\t\treturn err\n\t}\n\treturn ensureDependenciesIDPrimaryKey(ctx, db)\n}\n\n// backfillDependenciesID fills in any dependencies.id still NULL with\n// depid.New(issue_id, target) -- the same deterministic id every insert path\n// and the post-migration rekeyDependencyIDs pass use (dep_id_backfill.go) --\n// so rows with real edges get a real, cross-clone-stable id rather than a\n// throwaway placeholder, and rekeyDependencyIDs finds nothing left to correct","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/migration_repairs.go#L413-L449","documentation":"This wraps failure of schemaColumnExists when ensureDependenciesIDColumn checks whether `dependencies.id` exists as part of the migration-0053 repair (adding a CHAR(36) id, backfilling it, and keying it). Each step re-verifies its own target state, so a failing inspection aborts the repair before any DDL. It means the metadata query failed, not that the column is missing.","triggerScenarios":"repairV53RigAndSplitTargets (or the direct unit tests) invokes ensureDependenciesIDColumn and schemaColumnExists errors on the dependencies table — lost connection, privilege denial on column metadata, or driver failure.","commonSituations":"Under-privileged repair users; network interruption during the multi-step 0053 repair; database restarted between repair steps in CI.","solutions":["Resolve the wrapped driver error first (connectivity/privileges)","Grant the repair user metadata-read access","Re-run the repair — every step re-verifies state, so it is safe to retry from the start"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"var n int\nif err := db.QueryRowContext(ctx,\n    \"SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'dependencies' AND column_name = 'id'\").Scan(&n); err != nil {\n    return fmt.Errorf(\"cannot inspect dependencies columns: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := repairV53RigAndSplitTargets(ctx, db); err != nil {\n    if strings.Contains(err.Error(), \"checking dependencies.id\") {\n        db = reconnect(db)\n        return repairV53RigAndSplitTargets(ctx, db) // safe: steps re-verify\n    }\n    return err\n}","preventionTips":["Verify metadata access before starting the 0053 repair","Use a stable connection; reconnect on transient failures and retry","Remember each repair step re-verifies state, so full re-runs are safe","Test the repair with a restricted-privilege user to surface gaps"],"tags":["database","schema-migration","mysql","introspection"],"backgroundTag":"schema-introspection-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}