diff --git a/packages/shade-transfer/src/engine.ts b/packages/shade-transfer/src/engine.ts index eb22074..6fb3f8a 100644 --- a/packages/shade-transfer/src/engine.ts +++ b/packages/shade-transfer/src/engine.ts @@ -451,7 +451,7 @@ export class TransferEngine { for (const q of queues.values()) q.abort(err); throw err; } - await Promise.all(workers); + await settleWorkers(workers); } private async runRoundRobinUploadResumed( @@ -518,7 +518,7 @@ export class TransferEngine { for (const q of queues.values()) q.abort(err); throw err; } - await Promise.all(workers); + await settleWorkers(workers); } /** @@ -774,7 +774,7 @@ export class TransferEngine { for (const q of queues.values()) q.abort(err); throw err; } - await Promise.all(workers); + await settleWorkers(workers); } private async runRoundRobinUpload(state: OutgoingState): Promise { @@ -851,7 +851,7 @@ export class TransferEngine { for (const q of queues.values()) q.abort(err); throw err; } - await Promise.all(workers); + await settleWorkers(workers); } private async runLaneWorker( @@ -1596,3 +1596,25 @@ function snapshotIncomingLanes( } return out; } + +/** + * Wait for every lane worker, then report the first real failure. + * + * `Promise.all` rejects the moment one worker throws and leaves the others + * running with nobody holding their rejections — so an abort, which fails + * every lane at once, produces one propagated error and N-1 unhandled ones. + * Bun counts an unhandled rejection as a test failure, which is how the resume + * test failed roughly one run in fourteen on 08.09.2026 with a + * `TransferAbortError` the test had already caught through the handle. + * + * An abort is not the interesting failure when a real one is present, so a + * genuine error is preferred over `TransferAbortError` when both occurred. + */ +async function settleWorkers(workers: Promise[]): Promise { + const results = await Promise.allSettled(workers); + const errors = results + .filter((r): r is PromiseRejectedResult => r.status === 'rejected') + .map((r) => r.reason); + if (errors.length === 0) return; + throw errors.find((e) => !(e instanceof TransferAbortError)) ?? errors[0]; +}