{"record":{"id":"8b550f98075f7a25","repo":"gastownhall/beads","slug":"committing-migration-s-w","errorCode":null,"errorMessage":"committing migration %s: %w","messagePattern":"committing migration (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/schema.go","lineNumber":1690,"sourceCode":"\t\t\treturn count, fmt.Errorf(\"migration %s: %w\", mf.name, err)\n\t\t}\n\t\tsum := sha256.Sum256(data)\n\t\tcontentHash := hex.EncodeToString(sum[:])\n\t\tif _, err := db.ExecContext(ctx, \"INSERT IGNORE INTO \"+src.cursorTable+\" (version, content_hash) VALUES (?, ?)\", mf.version, contentHash); err != nil {\n\t\t\treturn count, fmt.Errorf(\"recording %s in %s: %w\", mf.name, src.cursorTable, err)\n\t\t}\n\t\tcount++\n\n\t\t// commitEachStep's DOLT_ADD/DOLT_COMMIT is the expensive, fallible\n\t\t// part of this step on the production embedded path. The \"done\" line\n\t\t// (and its timing) must land after that commit succeeds, not before\n\t\t// it: printing \"done\" and then hitting a commit error would show an\n\t\t// operator a false completion, and timing that stopped before the\n\t\t// commit would understate the step's real cost. A failed commit\n\t\t// returns before either print statement below runs.\n\t\tif commitEachStep {\n\t\t\tif err := commitMigrationStep(ctx, db, src.cursorTable, mf.name, dirtyBeforeStep); err != nil {\n\t\t\t\treturn count, fmt.Errorf(\"committing migration %s: %w\", mf.name, err)\n\t\t\t}\n\t\t}\n\t\tfmt.Fprintf(stderr, \"  done (%.1fs)\\n\", time.Since(start).Seconds())\n\n\t\tif migrateStepFaultHook != nil {\n\t\t\tif err := migrateStepFaultHook(ctx, db, mf.version); err != nil {\n\t\t\t\treturn count, err\n\t\t\t}\n\t\t}\n\t}\n\treturn count, nil\n}\n\n// migrateStepFaultHook is a test-only seam. When non-nil it runs at the end of\n// each applied migration step (after the step's per-step commit on the\n// production path); returning an error aborts the pass, emulating a\n// crash/kill/timeout mid-migration so tests can prove the retry converges.\n// Production leaves it nil.","sourceCodeStart":1672,"sourceCodeEnd":1708,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/schema.go#L1672-L1708","documentation":"This error wraps a failure from commitMigrationStep when commitEachStep is enabled on the embedded Dolt path (internal/storage/schema/schema.go:1690). After a migration's SQL and cursor row succeed, the step is committed atomically (staging newly-dirtied tables plus the cursor row and running DOLT_COMMIT); if that commit fails, the pass aborts. Deliberately, the 'done' progress line is only printed after this commit succeeds, so this error means the migration is applied-but-uncommitted in the working set.","triggerScenarios":"commitMigrationStep fails for migration <name>: dirtyTables diff query errors, DOLT_ADD of a newly-dirtied table fails (e.g. unknown/corrupt table), or DOLT_COMMIT fails for reasons other than 'nothing to commit' (working-set conflicts, storage errors).","commonSituations":"Killed/locked embedded Dolt database leaving an inconsistent working set; an uncommitted pre-existing mutation on a table the migration touched that the pre-flight dirty guard did not catch; disk-full during Dolt commit; database file corruption after a crash.","solutions":["Read the wrapped inner error from DOLT_ADD/DOLT_COMMIT and address that specific Dolt error.","Run `bd doctor` to inspect and repair the embedded Dolt database / working set state.","Check disk space and that the .beads database files are writable; Dolt commits need space to write new commits.","If the working set contains unexpected dirty tables, commit or discard them deliberately (backup first) so the per-step commit can proceed.","Restore from backup and re-run migrations if the Dolt repository is corrupted."],"exampleFix":"// before: migration applied but uncommitted; retry fails on dirty working set\nbd migrate up  # committing migration 0104_xxx: table 'issues' has unexpected changes\n// after: diagnose and repair the embedded repo first\nbd doctor\n# resolve or discard the unexpected working-set changes (after backup)\ncp -rf .beads /tmp/beads-backup && bd doctor --fix\nbd migrate up","handlingStrategy":"retry","validationCode":"// Pre-flight: ensure a clean Dolt working set before migrating\nif out, err := dirtyTables(ctx, db, true); err != nil || len(out) > 0 {\n    return fmt.Errorf(\"refusing to migrate: dirty working set %v — run bd doctor\", out)\n}\nif free, _ := diskFree(\".beads\"); free < minFreeBytes {\n    return fmt.Errorf(\"insufficient disk space for Dolt commits\")\n}","typeGuard":null,"tryCatchPattern":"err := runMigrationsWithCommitEachStep(ctx, db)\nfor attempt := 1; attempt <= 3 && err != nil; attempt++ {\n    if !strings.Contains(err.Error(), \"committing migration \") {\n        break // not a per-step commit failure; don't retry\n    }\n    if fixErr := bdDoctorFix(ctx); fixErr != nil {\n        return fmt.Errorf(\"cannot repair working set: %w\", fixErr)\n    }\n    err = runMigrationsWithCommitEachStep(ctx, db) // retry converges per #4566\n}","preventionTips":["Run `bd doctor` before and after major upgrades.","Keep ample free disk space where the .beads Dolt repository lives.","Never rsync/copy a live .beads directory while bd is running.","Treat 'done' absence on stderr as the signal that the per-step commit failed and the pass aborted.","Keep per-step commits enabled (commitEachStep) so retries converge instead of stranding uncommitted migrations."],"tags":["database","migration","dolt","commit"],"backgroundTag":"dolt-commit-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}