fix(studio): fail closed on backup failures - #3961
Merged
Merged
Conversation
Fail closed Studio-server mutations when a backup snapshot reports an error. Return the existing backup-failed HTTP 500 response before any raw or structured mutation can change the original. Co-authored-by: miguel.sierra <229591595+miguel-heygen@users.noreply.github.com>
miguel-heygen
approved these changes
Sep 15, 2026
miga-heygen
reviewed
Sep 15, 2026
miga-heygen
left a comment
Contributor
There was a problem hiding this comment.
Post-merge advisory review (this PR is already merged into main). Findings flagged for follow-up, not gating.
Reviewed head 81302486 in an isolated worktree.
Strengths
packages/studio-server/src/routes/files.ts:2385-2386— the PUT guard sits after the version check and beforeftruncateSync, inside thetrywhosefinallycloses the fd, so the early return leaves the file untouched and the descriptor closed.files.ts:2434-2435— DELETE now returns before eitherrmSyncorunlinkSync.files.ts:459-464— the shared writer's| Responsereturn forces every caller to handle the early exit at the type level;tsc --noEmitconfirms no caller destructures the union unchecked.
Verified
- All five
writeMutationResultcallers (:478,:1316,:2772,:2833,:2972) propagate theResponseviainstanceofbefore destructuring. No other callers exist. - All
snapshotBeforeWritesites in the file now fail closed: the three fixed here, insert-composition:2502-2503(samebackup failed:shape, 500), split-batch:2658-2664(500), and the rename helper:368-370(throws). Status code is consistent at 500 across all of them. - Fixtures are deterministic:
.hyperframeswritten as a regular file makesmkdirSync(join(projectDir, ".hyperframes", "backup"), { recursive: true })inhelpers/backupJournal.ts:41throwENOTDIR, which is not in the swallowedENOENT/EISDIRset, soerroris always populated. - Mutation check: reverting each of the three guards to the old
console.warnindividually makes exactly its matching test fail (PUT → "fails PUT closed", DELETE → "fails DELETE closed", writer → "fails structured DOM mutations closed"); the other 78 stay green. Each test also asserts the original bytes are unchanged. - No behaviour change on the success path: the
backupPath/versionpayloads are built from the same values as before. - Local:
files.test.ts79/79,tsc --noEmitclean inpackages/studio-server.
Should-be-follow-up-ticket / notes (pre-existing, not introduced here)
- Directory DELETE (
:2436-2437) still proceeds without a backup becausesnapshotBeforeWritemapsEISDIRto a silentbackupPath: null. Same as before this PR; flagging only so it isn't mistaken for covered. - split-batch uses
Failed to create backup for …while the other routes usebackup failed: …. Cosmetic inconsistency, not introduced here.
Verdict: COMMENT (post-merge: Ready)
Reasoning: Every backup-error path in this file now returns 500 before any write or unlink, all callers of the shared writer handle the new early return, and each fix is pinned by a test that fails when the guard is reverted.
— Miga
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fail closed Studio-server mutations when a backup snapshot reports an error. Related: #3957.
Why
The raw file PUT and DELETE routes, and the shared mutation writer, previously continued after
snapshotBeforeWrite()returnedbackup.error, changing the original without a backup.How
Return HTTP 500 with the existing
{ error: "backup failed: ..." }shape before the raw writes/deletion. The shared writer returns that same response and all direct callers propagate it.Source links
Baseline (
e2d60cf27c80849cb6c0fa5c22be34c85b8f6e05):At resulting HEAD (
813024861aa518fc74ae11ab85af2ba17fb6c7ea):Test plan
Evidence
Before: the baseline links above show the three backup-error warning paths immediately followed by mutation. The supplied investigation confirmed each route returned HTTP 200 with
backupPath: nulland changed the original; do not rerun that reproduction.After:
/home/ubuntu/.bun/bin/bun run --cwd packages/studio-server test -- src/routes/files.test.ts --reporter=verbosepassed 79/79. The three linked ENOTDIR tests require HTTP 500 and an error beginningbackup failed: ENOTDIR:, then verify the original bytes are unchanged. Also passed/home/ubuntu/.bun/bin/bun x oxlint packages/studio-server/src/routes/files.ts packages/studio-server/src/routes/files.test.ts(0 warnings/errors) and/home/ubuntu/.bun/bin/bun run --cwd packages/studio-server typecheck.