fix(modal): a rejected deterministic archive closes its output descriptor once - #1215
Merged
Merged
Conversation
…ptor once When the output check failed after the gzip pipeline had finished, the error path destroyed the write stream and then closed the output descriptor again in finally. Destroying an fs.WriteStream closes its descriptor even with autoClose off, asynchronously, and the finished pipeline no longer waited for it. So the synchronous close ran first and the stream's close then hit a freed descriptor number: EBADF, or the close of whatever file had reused that number. CI saw it as 'EBADF: bad file descriptor, close' instead of the expected output-changed error in 'rejects a caller-visible output parent replaced after descriptor normalization'. Wait for the stream's close and do not close the descriptor again. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…rows events.once rejects when the stream emits 'error' before 'close'. A write error raised while an earlier failure was being handled then skipped the cleanup of the created output, left the descriptor to be closed again in finally, and replaced the original error. Listen for 'close' directly and absorb the stream's error, so the original error is the one reported. The test now identifies the output descriptor and requires exactly one close, so a leak fails it too (Greptile, on #1215). Co-Authored-By: Claude Opus 5.5 <[email protected]>
The strict type-aware lint (no-confusing-void-expression) rejects an arrow shorthand that returns resolve()'s void result. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This was referenced Sep 29, 2026
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.
Problem
packages/modal/test/deterministic-archive.test.ts› "rejects a caller-visible output parent replaced after descriptor normalization" fails intermittently. It failed in #1209's package gates lane:Root cause
writeDeterministicTarGzippasses its output descriptor tofs.createWriteStream(..., { fd, autoClose: false }). On failure, itscatchcallsoutput.destroy(), and itsfinallycloses the descriptor.fs.WriteStreamcloses its descriptor even withautoClose: false(checked on Node 24.21), and the close is asynchronous.await completionreturns at once. The synchronous close infinallythen runs before the stream's own close.EBADF, or, if the number was reused in the meantime, the close of an unrelated file.Change
In the
catch, the function waits for the destroyed stream'scloseevent. It then leaves the descriptor to the stream and does not close it again. The success path is unchanged: there the stream does not close the descriptor, andfinallycloses it once.The writer's only production caller is the Modal node provider, which draft #1197 deletes along with this file. This fix removes the CI flake whatever the decision on #1197.
Verification
fs.closeandfs.closeSyncand fails when a descriptor is closed twice. It fails onorigin/main3 of 3 times and passes here 6 of 6 times.deterministic-archive.test.ts: 9/9.tsc --noEmit, prettier, eslint andlint:strict:cipass.Greptile follow-up
events.oncerejects whenerrorcomes beforeclose. The catch now listens forclosedirectly and absorbs the stream's error. So the created output is still removed, the descriptor is never closed twice, and the original error is the one reported.fs.openSync) and requires exactly one close, so a leak fails it too (comment). Onorigin/mainit fails withexpected [ 24, 24 ] to have a length of 1 but got 2.🤖 Generated with Claude Code
The PR appears safe to merge; no new issue or outstanding previous finding was identified.
Summary
The PR waits for a rejected archive’s output stream to close before relinquishing its descriptor, and adds a test requiring that descriptor to close exactly once. The change since the previous review simplifies the close-event callback without changing its behavior.
Reviews (3) · Last reviewed commit: "style(modal): pass the close listener wi..."