fix: Avoid swap-file races when multiple plugin instances share a tadaOutputLocation#420
Open
macrozone wants to merge 1 commit into
Open
fix: Avoid swap-file races when multiple plugin instances share a tadaOutputLocation#420macrozone wants to merge 1 commit into
macrozone wants to merge 1 commit into
Conversation
…aOutputLocation
Multiple TS projects (e.g. in a monorepo extending a shared tsconfig)
can point their plugin instances at the same tadaOutputLocation. All
instances used the same swap-file path ('<output>.tmp'), so concurrent
regenerations raced: one instance renames the swap-file into place
while another still expects it, failing its rename and then throwing
ENOENT from the clean-up unlink — leaving the typings stale until a
manual regeneration.
The swap-file name is now unique per process and write, and clean-up
no longer masks the original rename error.
Fixes 0no-co/gql.tada#571
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: f24debb The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Author
|
thx fable... |
JoviDeCroock
reviewed
Jul 8, 2026
| // If the file exists, we write to a swap-file, then rename (i.e. move) | ||
| // the file into place. No try-catch around `writeFile` for proper | ||
| // directory/permission errors | ||
| const tempTarget = target + '.tmp'; |
Member
There was a problem hiding this comment.
This still races on the first write: if the output doesn't exist yet, concurrent instances all take this direct writeFile path. Can we use the unique swap+rename path for missing targets too?
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.
Summary
Fixes 0no-co/gql.tada#571.
When several TS projects configure the plugin with the same
tadaOutputLocation(e.g. a monorepo where ~10 tsconfigs extend one shared fragment), each plugin instance regenerates the same typings file.swapWriteused a single shared swap-file path (<output>.tmp) for all of them, so concurrent regenerations raced:<output>.d.ts.tmp.renamefails withENOENT, and its clean-upunlinkthrowsENOENTas well — which is the error that surfaces:After that, the in-editor tada types stay stale/broken until a manual CLI regeneration + TS server restart.
Fix
<output>.<pid>.<n>.tmp), covering both separate tsserver processes and multiple plugin instances within one process.renamestays atomic, so concurrent writers simply last-write-win without ever leaving a broken output.renameerror path is best-effort (.catch(() => {})), so a genuine rename failure is reported instead of being masked by the unlink error.Test
Added
test/unit/swapWrite.test.tswith a 20-way concurrent write test. Against the previous implementation it reproduces the exact error from the issue:With the fix, all writers resolve, the target contains one full write, and no
.tmpfiles are left behind.🤖 Generated with Claude Code