Skip to content

fix(zip): observe writer promises so ZIP errors surface to the pipeline#114

Open
dobby-coder[bot] wants to merge 1 commit into
mainfrom
fix/107-zip-writer-unhandled-rejections
Open

fix(zip): observe writer promises so ZIP errors surface to the pipeline#114
dobby-coder[bot] wants to merge 1 commit into
mainfrom
fix/107-zip-writer-unhandled-rejections

Conversation

@dobby-coder

@dobby-coder dobby-coder Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Closes #107

Problem

In createZipReadable (src/util/zip.ts) the writer promises were fire-and-forget:

for (const f of files) {
  const s = createFileReadable(f);
  writer.write({ name: f.name, lastModified: f.lastModified, stream: () => s }); // promise ignored
}
writer.close(); // promise ignored

When a source file read fails (or conflux rejects), those promises reject with no handler and surface as unhandled rejections instead of reaching the consumer of the returned readable. This contradicts the deliberate stream-error handling elsewhere in the pipeline (awaitAllOrAbort in src/crypto/encrypt.ts).

Fix

  • Accumulate every writer.write(...) promise plus the writer.close() promise.
  • Observe them together with Promise.all(...).catch(...), so no rejection escapes unhandled (Promise.all attaches a handler to each input, so late-rejecting losers are observed too).
  • On failure, writer.abort(err) errors the linked readable, so anything piping it (e.g. sealStream) sees the failure rather than a silently truncated archive.

The happy path is unchanged: the same readable is returned and writes are still enqueued without blocking, so backpressure behaviour is identical.

Test

Added a failing-source regression test in tests/zip.test.ts that feeds a File whose read rejects. It asserts:

  1. draining the returned readable rejects (the consumer observes the error), and
  2. no unhandledRejection leaks to the process.

Verified it fails before the fix (two unhandled rejections captured) and passes after. Full suite: 206 passing; tsc --noEmit clean.

createZipReadable fired writer.write()/writer.close() without observing
their promises. When a source file read failed or conflux rejected, those
promises rejected with no handler and surfaced as unhandled rejections
instead of reaching the consumer of the returned readable.

Accumulate the write promises plus the close promise and observe them
with Promise.all; on failure, abort the writer, which errors the linked
readable so anything piping it (e.g. sealStream in encrypt.ts) sees the
failure rather than a silently truncated archive.

Adds a failing-source regression test that asserts no unhandled rejection
leaks while the returned readable still rejects on drain.

Closes #107

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@dobby-coder dobby-coder Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: approve

Rules Dobby 2 sign-off — PR #114 (fixes #107)

Consolidated review (rule check + Review Dobby 2 findings): no blocking issues.

  • Correctness: Promise.all(pending).catch(...) observes every writer.write() promise plus writer.close(), so no rejection escapes unhandled; Promise.all attaches a handler to each input, so late-rejecting losers are observed too. On failure writer.abort(err) errors the linked readable, so a consumer (e.g. sealStream) sees the failure instead of a truncated archive. Happy path and backpressure are unchanged (writes still enqueued without awaiting, matching the original).
  • Test: the added tests/zip.test.ts regression feeds a File whose slice().arrayBuffer() rejects; createFileReadable propagates that through pull, erroring the source → conflux errors the writer → the observed promise rejects. It asserts the drained readable rejects and that no unhandledRejection leaks. Verified the error path is real.
  • Rules: conventional-commit title (fix(zip): ...) satisfies the pr-title workflow; regression test present (tests-required-on-fixes); Closes #107 correct for a full fix; prose clean.

Review Dobby 2: 0 findings, full suite 206 passing, tsc clean.

@dobby-coder
dobby-coder Bot marked this pull request as ready for review July 14, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

createZipReadable ignores writer.write/close promises (unhandled rejections on ZIP errors)

0 participants