Summary
addPage, addAction, and addDatabase read shared project files, compute replacements, and write them without an atomic compare-and-write boundary. Concurrent commands can therefore report success while one silently overwrites another command's route, action registry, authorization, environment, or package-manifest edit.
Triage
Confirmed, Medium-High. The issue was reproduced with two concurrent add commands: both returned exit code 0 while only one registration survived. The generated but unregistered artifact is silent data loss. This affects ordinary parallel terminals, scripts, and retries.
Reproduction
await Promise.all([
runAddCli(["page", "alpha", "--cwd", root], alphaIo),
runAddCli(["page", "beta", "--cwd", root], betaIo),
]);
The same read-modify-write shape exists for action registries/authorizations and database manifest/environment edits.
Required implementation details
- A fresh-content comparison alone is insufficient: two writers can compare the same value before either rename. Protect the expected-content comparison and the entire staged replacement transaction with deterministic, cross-process file locks acquired in sorted order.
- Reject a stale plan before staging or replacing any file, so a failed command cannot leave an orphan page, action, database file, or partial shared-file edit.
add page must guard the route file content observed at plan time.
add action must snapshot both registries before descriptor discovery, then guard both snapshots. Capturing them after discovery leaves a second interleaving where stale computed actions can validate against a newer registry.
add database must guard both package.json and the optional .env.example content observed at plan time.
- Locks must be released after success and failure, and abandoned locks must have a bounded recovery path.
Acceptance criteria
Definition of done
Severity
Medium-High — silent generated-registration data loss with a false success signal.
Found via a deep adversarial pass on @askrjs/cli shared-file mutations.
Summary
addPage,addAction, andaddDatabaseread shared project files, compute replacements, and write them without an atomic compare-and-write boundary. Concurrent commands can therefore report success while one silently overwrites another command's route, action registry, authorization, environment, or package-manifest edit.Triage
Confirmed, Medium-High. The issue was reproduced with two concurrent add commands: both returned exit code 0 while only one registration survived. The generated but unregistered artifact is silent data loss. This affects ordinary parallel terminals, scripts, and retries.
Reproduction
The same read-modify-write shape exists for action registries/authorizations and database manifest/environment edits.
Required implementation details
add pagemust guard the route file content observed at plan time.add actionmust snapshot both registries before descriptor discovery, then guard both snapshots. Capturing them after discovery leaves a second interleaving where stale computed actions can validate against a newer registry.add databasemust guard bothpackage.jsonand the optional.env.examplecontent observed at plan time.Acceptance criteria
smiggleworth.File changed before writingdiagnostic before creating or replacing any transaction artifact.add pageguards_routes.tsx; a deterministic concurrent-invocation regression proves no registration is silently lost and no failed-command page is orphaned.add actionguardsaction-registry.tsandaction-authorizations.ts; deterministic regressions cover concurrent invocations and a registry change during descriptor discovery.add databaseguardspackage.jsonand.env.example; a regression proves external manifest changes survive and no database scaffold is partially written.Definition of done
@askrjs/cli@0.0.25.Severity
Medium-High — silent generated-registration data loss with a false success signal.
Found via a deep adversarial pass on
@askrjs/clishared-file mutations.