fix(multiscan): keep scan outcomes when checkout cleanup fails - #218
Open
rohanpoudel2 wants to merge 2 commits into
Open
fix(multiscan): keep scan outcomes when checkout cleanup fails#218rohanpoudel2 wants to merge 2 commits into
rohanpoudel2 wants to merge 2 commits into
Conversation
The per-repository worker awaited checkout removal in a `finally` that ran
before the attempt receipt was appended:
} catch (error) {
if (options.signal?.aborted === true) options.signal.throwIfAborted();
failure = redactedErrorMessage(error);
} finally {
await rm(checkout, { recursive: true, force: true });
}
const status = failure === undefined ? "completed" : "failed";
await appendReceipt(...);
`force: true` ignores a checkout that is already gone, but it does not
suppress EACCES, EPERM or EBUSY. When the removal rejected, its filesystem
error replaced the outcome the worker had just captured and execution never
reached `appendReceipt`, so the real scan failure was lost, the campaign
surfaced a confusing removal error instead, and the attempt was left
unrecorded for resume.
Capture the removal failure rather than throwing it. The attempt keeps the
status its scan earned, always gets a receipt, and reports the removal
failure alongside any scan failure so a leftover checkout is not silently
dropped. The next attempt removes the checkout again inside the try block,
so a leftover that outlives this run fails there rather than being scanned
as if it were fresh. Cancellation is unaffected: the abort check stays in
the catch, ahead of the cleanup.
Fixes openai#211
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.
Fixes #211
Problem
The per-repository worker in
runCampaignawaited checkout removal in afinallythat ran before the attempt receipt was appended:force: trueignores a checkout that is already gone, but it does not suppressEACCES,EPERMorEBUSY— a still-open handle on Windows, or a parent directory that is no longer writable. When the removal rejected, its filesystem error replaced the outcome the worker had just captured, and execution never reachedappendReceipt. Three things went wrong at once: the real scan failure was lost, the campaign surfaced a confusing removal error in its place, and the attempt was never recorded, so resume had no receipt for it.Change
Capture the removal failure instead of throwing it. The attempt keeps the status its scan earned, always gets a receipt, and reports the removal failure alongside any scan failure, so a leftover checkout is still surfaced rather than silently dropped.
A cleanup failure on a scan that otherwise succeeded therefore produces a
completedreceipt carrying anerrorfield. That combination is already representable —erroris optional onMultiscanReceiptand independent ofstatus— and it is the honest record: the scan did complete, so resume correctly counts it as done rather than re-running billed model work, while the operator still learns that a checkout was left behind. The CLI's existing progress renderer prints it without any change:A leftover checkout cannot be mistaken for a fresh one later: every attempt removes the checkout again at the top of the
try, so a leftover that outlives this run fails there instead.Cancellation is unaffected — the
signal.throwIfAborted()check stays in thecatch, ahead of cleanup.Verification
Two regression tests in
tests-ts/multiscan.test.tsinject a failing removal throughmock.module("node:fs/promises", …), the same seamtests-ts/api.test.tsalready uses to test cleanup failures. The injection only rejects for the checkout path and only once the scan is over, so the removal at the top of thetrystill runs normally. That keeps the tests deterministic and cross-platform, rather than depending onicaclsor on POSIX permission semantics.Against the unfixed worker, the injected
EACCESescapesrunMultiscanentirely, exactly as reported:The tests assert both halves of the defect: that
ORIGINAL_SCAN_FAILUREsurvives, and that the receipt is written at all. With the fix:Full suite: 719 pass / 5 skip / 0 fail.
pnpm run typesandpnpm run formatare clean.Same-class audit
The other
finallyblocks inmultiscan.ts— the supervisor lock release and the clientclose()path — do not have this shape: they run where a throw is the intended outcome, or already guard their own failures. This was the only cleanup that could silently overwrite a captured result.