fix(migrate): report a failing migration once, naming the migration - #395
Conversation
|
@claude /review |
|
Claude finished @thecodedrift's task in 5m 28s —— View job Review completeReview mode: incremental — read 0 prior review thread(s) before reviewing.
SummaryThe core fix is sound: folding the I found one correctness issue worth fixing before merge, plus two lower-severity latent gaps in the same "don't lose or double the message" spirit the PR is about — posted inline:
None of these affect the PR's stated motivating cases (migration 4's coded/uncoded failure on the very first pending migration), which is why the tests pass; #1 is the one I'd actually want addressed, since it reintroduces a worse failure mode (total silence) than the one being fixed, in a case one step removed from the one under test. |
All three inline findings are answered and resolved on their threads; no code changed. The short version: finding 1 rests on migrations being unsafe to re-run, and they are not. Verified against the code for all nine, not against the docblocks, and then measured: a version-3 fixture with the full pre-0004 legacy tree migrated to 9, re-stamped to 3 and re-run so 4-9 genuinely execute over the migrated tree — four passes, each — AI Coding Agent |
runMigrations printed `Migration N failed: <message>` and then rethrew the original, so the caller printed the same text again without the migration number. Fold the prefix into the rethrown error instead: one string, one printer. A CLIError keeps its code and reported flag; anything else stays a plain Error (so it still classifies as INTERNAL_ERROR) with the original as `cause`. Also give `init --json` the error envelope its siblings have. A throwing migration left stdout empty, so a machine consumer had only the exit code. The manifest stamp in the same catch is untouched.
8bc0c71 to
3efc275
Compare
runMigrationslogged a failing migration and then rethrew the original error, so whoever owned the surface printed the same text a second time — once with the migration number, once without. The longer and more useful the refusal, the worse it read.The issue's motivating case (migration 9's rule-id collision) no longer refuses; #388 made it rename in place. The bug is still reachable through migration 4's
SCAFFOLD_CONFLICTrefusal, and through any migration that throws at all, so this is fixed as generic hygiene rather than for one migration.Before
A
.taskless/at schema version 3 with a file where an engine directory belongs, theninit:Not specific to coded errors — a file at
.taskless/rulesreaches a baremkdirand doubles the same way:After
The prefix rides on the rethrown error, so there is one string and one printer:
The migration number is kept on both branches, including the unexpected fault. Losing it on precisely the errors we understand least is the wrong trade, and a migration's own message names paths and never itself — nothing else says which migration refused.
A
CLIErroris rewrapped with its originalcode(SCAFFOLD_CONFLICT) andreportedflag, becausecodeis what telemetry attributes on and re-coding would flatten a deliberate refusal into the same bucket as a crash. Anything else stays a plainErrorwith the original ascause, so it still classifies asINTERNAL_ERRORexactly as before.Second user-visible fix:
init --jsonnow emits an error envelopeFolded in because the issue's own requirement — the migration number reaching a consumer — is unsatisfiable without it. When a migration threw,
init --jsonwrote nothing at all to stdout and put prose on stderr, leaving a machine consumer with only the exit code. Its siblingupdatehas carriedmakeErrorEnvelopeall along; this is the same shape, not a second one.check --jsonandverify --jsonnever reach a failing migration (requireCurrentSchemawalls them first withSCAFFOLD_MIGRATION_REQUIRED), soinitand the wizard were the only surfaces where this was visible. The wizard already deduped viacancel(error.message)and now shows the migration number there too.What is deliberately unchanged
catch(write at last successful version so completed migrations do not re-run) is byte-identical. The diff there is 4 deleted lines above and below it and nothing else.initreport success over a half-migrated tree.Tests
New
packages/cli/test/migration-failure.test.ts, spawning the built CLI. It asserts exit code 1, then thatCannot partition/EEXISToccurs exactly once and that the occurrence namesMigration 4, plus the--jsonenvelope for both the coded and uncoded cases. It spawns rather than unit-testingrunMigrationsbecause the bug lives in the seam between the two layers, and because only a real process has the exit code that guards against swallowing.pnpm build,pnpm typecheck,pnpm lint,pnpm test(103 files / 1702 tests) all pass.Fixes #389