{"record":{"id":"754fe6206d2dc99e","repo":"gastownhall/beads","slug":"adding-wisp-dependencies-id-for-the-0058-repair","errorCode":null,"errorMessage":"adding wisp_dependencies.id for the 0058 repair: %w","messagePattern":"adding wisp_dependencies\\.id for the 0058 repair: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/wisp_dep_forward_repair.go","lineNumber":400,"sourceCode":"// composite-keyed shape without one, and ignored/0005 -- the only migration that\n// adds id to a legacy store -- drops the generated column in the same guarded\n// run. There is no state carrying both, so this never has to reconcile one.\n//\n// DEFAULT (UUID()) is correct here even though a fully migrated table carries\n// no default: ignored/0010 drops it, and this repair can only reach the ADD\n// COLUMN branch on a store whose ignored cursor is still behind 0005 (that is\n// what \"the generated column is still present\" means), hence behind 0010. The\n// default is therefore always dropped downstream in the same pass, which is why\n// ignored/0005 mints the column the same way rather than special-casing it.\nfunc ensureWispDepSurrogateKey(ctx context.Context, db DBConn) error {\n\thasID, err := schemaColumnExists(ctx, db, wispDepTable, \"id\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !hasID {\n\t\tif _, err := db.ExecContext(ctx,\n\t\t\t\"ALTER TABLE wisp_dependencies ADD COLUMN id CHAR(36) NOT NULL DEFAULT (UUID()) PRIMARY KEY FIRST\"); err != nil {\n\t\t\treturn fmt.Errorf(\"adding wisp_dependencies.id for the 0058 repair: %w\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\t// id survived a crash but its key did not (or the column predates the\n\t// key): add the key alone rather than re-adding the column.\n\thasPK, err := schemaHasPrimaryKey(ctx, db, wispDepTable)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !hasPK {\n\t\tif _, err := db.ExecContext(ctx, \"ALTER TABLE wisp_dependencies ADD PRIMARY KEY (id)\"); err != nil {\n\t\t\treturn fmt.Errorf(\"adding the wisp_dependencies id primary key for the 0058 repair: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n","sourceCodeStart":382,"sourceCodeEnd":418,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/wisp_dep_forward_repair.go#L382-L418","documentation":"Wraps a failure when adding the final surrogate key column to wisp_dependencies (`ALTER TABLE ... ADD COLUMN id CHAR(36) NOT NULL DEFAULT (UUID()) PRIMARY KEY FIRST`) when the id column is missing. This is the first step of the final 0058 shape; a failure leaves the table without its primary identity, so the repair aborts.","triggerScenarios":"ensureWispDepSurrogateKey finds no `id` column and the ADD COLUMN ... PRIMARY KEY DDL fails — DEFAULT (UUID()) expression unsupported by the engine/version, duplicate implicit PK conflict, privilege denial, or lock timeout.","commonSituations":"Older MySQL/Dolt versions without functional DEFAULT expressions; repair run on a server that rejects expression defaults; DB user lacking ALTER/INSERT needed to backfill UUIDs; concurrent writers blocking the ALTER.","solutions":["Check the wrapped cause: unsupported DEFAULT (UUID()) → upgrade Dolt/MySQL to a version supporting expression defaults.","Re-run the guarded repair after fixing the engine/privilege issue.","Grant ALTER (and privilege to evaluate UUID backfill) to the migration user.","Retry during a maintenance window with no concurrent writers on wisp_dependencies."],"exampleFix":"// before: fails on engines without expression defaults\nALTER TABLE wisp_dependencies ADD COLUMN id CHAR(36) NOT NULL DEFAULT (UUID()) PRIMARY KEY FIRST\n// after: upgrade the engine, or add then backfill manually\nALTER TABLE wisp_dependencies ADD COLUMN id CHAR(36) NULL FIRST;\nUPDATE wisp_dependencies SET id = UUID() WHERE id IS NULL;\nALTER TABLE wisp_dependencies MODIFY id CHAR(36) NOT NULL, ADD PRIMARY KEY (id);","handlingStrategy":"try-catch","validationCode":"// verify engine supports functional column defaults before repair\nvar version string\nif err := db.QueryRowContext(ctx, \"SELECT VERSION()\").Scan(&version); err != nil {\n    log.Fatalf(\"cannot read engine version: %v\", err)\n}\nlog.Printf(\"engine %s — DEFAULT (UUID()) requires MySQL 8.0.13+/current Dolt\", version)","typeGuard":null,"tryCatchPattern":"if err := repairWispDependenciesForwardShape(ctx, db); err != nil {\n    if strings.Contains(err.Error(), \"adding wisp_dependencies.id\") {\n        // likely expression-default unsupported or privilege denial; upgrade engine or add+backfill id manually, then re-run\n        return fmt.Errorf(\"surrogate key step failed; check engine version/privileges: %w\", err)\n    }\n    return err\n}","preventionTips":["Run a Dolt/MySQL version supporting DEFAULT (UUID()) expression defaults.","Grant ALTER to the migration user.","Run the repair with no concurrent writers to avoid metadata-lock timeouts.","Back up the database before schema upgrades."],"tags":["database","schema-repair","migration","primary-key","ddl"],"backgroundTag":"schema-migration-ddl-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}