Repository navigation
fix(sdk): replace an upload's meta.json atomically - #66
Merged
Merged
Conversation
…sees it half written
The uploads plugin rewrote meta.json in place. A status poll or a superseded run that read it
mid-write parsed a partial file, and the stored-upload route test failed on exactly that.
`FileStore.write` takes `{ atomic: true }`: it writes a pending file beside the target and renames
it over, and removes the pending file if the rename fails. A platform without `rename` writes in
place, and its `writeFile` must then be atomic to readers. The uploads plugin writes meta.json
this way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The containment check follows the final link, but an atomic write replaces that entry in its own directory instead of writing through it. A link chain that left the roots and pointed back inside let it create and replace files outside. An atomic write now also checks the entry's directory. The write-then-rename steps are shared with the event store's log replacement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A run that outlived its runtime renames its pending meta.json between the cleanup's listing and the removal, which then failed and abandoned the re-run. A removal that fails on a file no longer there now counts as done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
Review round 1Reviewer: Claude Opus 5.5 subagent (independent code review), one reviewer
Checked and fine:
|
Member
Author
Response to review round 1
Verified on 2b95639:
|
… following links `exists` follows links, so a dangling link whose removal failed read as gone and the cleanup went on with it in place. The cleanup lists the directory again instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
Review round 2Reviewer: Claude Opus 5.5 subagent (independent code review), one reviewer All four round 1 fixes hold. Removing each fix on a clean copy makes its test fail.
|
Member
Author
Response to review round 2
Only minor items, so no new round. Verified on fd57e1e: |
matej21
marked this pull request as ready for review
November 6, 2026 14:28
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.
Why
The uploads plugin rewrote an upload's
meta.jsonin place. Anything that read the file during that write parsed a partial file: a status poll, or a superseded run checking whether it was superseded.The stored-upload route test hits exactly this.
transport/http/routes/upload.test.ts› "processes a stored upload from the host blob store with the host preprocessors" pollsmeta.jsonand fails with "JSON Parse error". It fails in the fullbun run testonmainand passes when the file runs alone.What
FileStore.write(path, content, { atomic: true }):.<name>.pending-<uuid>) in the same directory and renames it over the target;FileEventStore.replaceLog(platform/replace-file.ts).rename(it is optional onFileSystem) writes in place. ItswriteFilemust then be atomic to readers. TheFileSystem.renamedoc says so.RuntimeFileStorepasses the option through.WriteOptionsis exported.meta.jsonatomically.A retry clears an interrupted run's files except
meta.jsonand the upload itself, so a pending file a crash left behind goes with them. A superseded run whose pending file is cleared that way fails its rename instead of overwritingmeta.json. If such a run renames its pending file away between the cleanup's listing and the removal, the cleanup lists the directory again and moves on; a dangling link that is still listed keeps the failure.Tests
file-store.test.ts› "SessionFileStore atomic write":writeFilelands in two halves. A read in between sees the old content, and no pending file is left afterwards.renamewrites in place.upload.test.ts› "never lets a status poll read a meta.json that is half written": the stored-upload flow on a filesystem whose writes land in two halves 30 ms apart, polled every 5 ms.uploads-retention.integration.test.ts› "clears for a re-run past a file that another writer renamed away between the listing and its removal".Mutations, each caught:
meta.jsonwithoutatomic(the route test fails 3/3);atomic(2 fail);Local on fd57e1e:
bun run ts:build,bun run lintand the test type checks are clean.bun run test: 2160 pass, 0 fail.Status
🤖 Generated with Claude Code