Skip to content

fix: swallow reader.cancel rejection when stream source already errored - #627

Open
mattapperson wants to merge 2 commits into
mainfrom
claude/fix-stream-cancel-abort-error
Open

mattapperson wants to merge 2 commits into
mainfrom
claude/fix-stream-cancel-abort-error

Conversation

@mattapperson

@mattapperson mattapperson commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Fixes #615
Fixes #625

withStreamErrorHandling's cancel hook called reader.cancel(reason) and dropped the promise. When a streaming request is aborted, fetch errors the response body while the consumer cancels the wrapper; if the body's error lands first, reader.cancel() rejects with the AbortError and Node reports an unhandled rejection (crashing processes under the default --unhandled-rejections=throw).

The hook now returns reader.cancel(reason).catch(() => {}), matching the guard already used in pull. This covers both chat and completion streaming, which share the helper.

Added a regression test in src/utils/with-stream-error-handling.test.ts that errors the source with an AbortError, cancels the wrapper, and asserts no unhandledRejection fires. It fails on main and passes with the fix (node + edge).

Checklist

  • I have run pnpm stylecheck and pnpm typecheck
  • I have run pnpm test and all tests pass
  • I have added tests for my changes (if applicable)
  • I have updated documentation (if applicable)

Changeset

  • I have run pnpm changeset to create a changeset file

Devin Review

devin-ai-integration[bot]

This comment was marked as resolved.

Cancelling the wrapped stream after the source errored (e.g. aborted fetch)
made reader.cancel() reject with no handler, crashing Node with an
unhandled AbortError. Affects chat and completion streaming.

Fixes #615
Fixes #625
Add an offline e2e/issues regression test that drives chat and completion
doStream through a mocked fetch, errors the body with an AbortError at
queue depths 0-6, cancels, and asserts no unhandledRejection (fails at
depth 2 without the fix).

Also skip controller access in pull once the wrapper has been cancelled,
so a read settling after cancel no longer passes a spurious
'Controller is already closed' TypeError to onError.
@mattapperson
mattapperson force-pushed the claude/fix-stream-cancel-abort-error branch from 64b83ca to 2663716 Compare November 4, 2026 14:35
});

it('should not leak an unhandled rejection when cancelled after the source errored', async () => {
// Regression test for #615 / #625: an aborted fetch errors the response

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] do we want the issue numbers still in the comment here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants