Skip to content

fix(http): preserve response in error when stream is aborted after headers#10708

Merged
jasonsaayman merged 8 commits intoaxios:v1.xfrom
afurm:fix/stream-abort-preserve-response
Apr 28, 2026
Merged

fix(http): preserve response in error when stream is aborted after headers#10708
jasonsaayman merged 8 commits intoaxios:v1.xfrom
afurm:fix/stream-abort-preserve-response

Conversation

@afurm
Copy link
Copy Markdown
Contributor

@afurm afurm commented Apr 14, 2026

What

When a server sends response headers but aborts the stream before completing the body, axios now attaches the response object to the thrown AxiosError. This allows consumers to access response metadata (status, headers) for debugging, retry logic, or user messaging.

How

Modified the aborted and error event handlers in lib/adapters/http.js to attach the response object to the error before rejecting.

Testing

The fix can be tested with the reproduction case from the issue:

  1. Server sends headers, then calls res.destroy()
  2. Client catches error and checks err.response — now populated

Closes #6935


Summary by cubic

Preserves the Node http adapter’s response on stream aborts and decode errors after headers are sent so callers can read status and headers. error.status now mirrors the response status.

Description

  • Summary of changes
    • Pass response to AxiosError on aborted and stream error events in the Node http adapter.
    • This ensures error.response is set and error.status === response.status.
    • Rebased on latest v1.x; no behavior changes beyond error shape.
  • Reasoning
    • Consumers need status and headers for retries and debugging when the body fails after headers.
  • Additional context
    • Stream error handling is normalized; no impact on successful responses.

Docs

  • Update /docs/ for Node error handling: when a response stream is aborted or decoding fails after headers, axios includes error.response (status, headers) and sets error.status to the response status.

Testing

  • Updated unit tests:
    • Gunzip decode error: asserts error.response.status === 206, x-stream-error: yes, and error.status === 206.
    • Aborted chunked response: asserts error.response.status === 200, x-stream-aborted: yes, and error.status === 200.

Semantic version impact

  • Patch.

Written for commit 1dfadfa. Summary will update on new commits. Review in cubic

…aders

When a server sends response headers but aborts the stream before completing
the body, axios now attaches the response object to the error. This allows
consumers to access response metadata (status, headers) for debugging,
retry logic, or user messaging.

Fixes axios#6935
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file

Confidence score: 4/5

  • This PR is likely safe to merge, but there is a moderate consistency risk from how AxiosError is built in lib/adapters/http.js.
  • In lib/adapters/http.js, attaching response after constructing AxiosError skips constructor normalization of status, which can produce inconsistent error objects and affect downstream error handling.
  • Pay close attention to lib/adapters/http.js - ensure AxiosError is initialized in a way that preserves normalized status and consistent error shape.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/adapters/http.js">

<violation number="1" location="lib/adapters/http.js:826">
P2: Response is attached to AxiosError after construction, bypassing constructor normalization that sets `status`, causing inconsistent error objects.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread lib/adapters/http.js Outdated
@jasonsaayman jasonsaayman added commit::fix The PR is related to a bugfix status::changes-requested A reviewer requested changes to the PR labels Apr 25, 2026
@jasonsaayman jasonsaayman added the priority::medium A medium priority label Apr 25, 2026
Copy link
Copy Markdown
Member

@jasonsaayman jasonsaayman left a comment

Choose a reason for hiding this comment

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

Thanks for picking this up. Please check the feedback:

1. err.status is left undefined (correctness bug)

AxiosError's constructor normalises status from response:

if (response) {
  this.response = response;
  this.status = response.status;
}
(lib/core/AxiosError.js:49-52)

Both patched paths construct the error without response, then assign err.response = response afterwards. That bypasses the normalisation, so err.status stays undefined even though err.response.status is populated. AxiosError.toJSON() serialises the status directly, so logged/serialised errors will be inconsistent.

The convention already used elsewhere in this same file (see line 930) is to pass the response as the 5th argument:

responseStream.on('aborted', function handlerStreamAborted() {
  if (rejected) return;
  const err = new AxiosError(
    'stream has been aborted',
    AxiosError.ERR_BAD_RESPONSE,
    config,
    lastRequest,
    response
  );
  responseStream.destroy(err);
  reject(err);
});

responseStream.on('error', function handleStreamError(err) {
  if (req.destroyed) return;
  reject(AxiosError.from(err, null, config, lastRequest, response));
});

This removes the post-hoc assignment, sets status correctly, and drops the now-unnecessary if (response) guard (which is unreachable anyway, response is const-declared at line 839 in the same closure, before the listeners are attached).

2. Please add a regression test

This closes a long-standing issue (#6935), and there are no tests. At a minimum, one test per path:

  • aborted: server writes headers, then res.destroy() mid-body → assert err.response.status, err.response.headers, and err.status.
  • error: same setup with an emitted stream error → same assertions.

Asserting err.status in particular would have caught issue #1.

3. Scope clarification (optional, for the PR description)

This fix only applies to buffered responses. When responseType: 'stream', the adapter takes the early branch at line 847, and these listeners are never attached. Worth calling out so users with streamed responses don't expect the same behaviour. Also worth noting that err.response.data will be undefined on the aborted path since data is only assigned in the 'end' handler; only metadata (status/headers) survives, which matches the stated intent.

Once 1 and 2 are addressed, I think this is good to go. Patch-level change, no breaking concerns.

@afurm
Copy link
Copy Markdown
Contributor Author

afurm commented Apr 25, 2026

Thanks for picking this up. Please check the feedback:

1. err.status is left undefined (correctness bug)

AxiosError's constructor normalises status from response:

if (response) {
  this.response = response;
  this.status = response.status;
}
(lib/core/AxiosError.js:49-52)

Both patched paths construct the error without response, then assign err.response = response afterwards. That bypasses the normalisation, so err.status stays undefined even though err.response.status is populated. AxiosError.toJSON() serialises the status directly, so logged/serialised errors will be inconsistent.

The convention already used elsewhere in this same file (see line 930) is to pass the response as the 5th argument:

responseStream.on('aborted', function handlerStreamAborted() {
  if (rejected) return;
  const err = new AxiosError(
    'stream has been aborted',
    AxiosError.ERR_BAD_RESPONSE,
    config,
    lastRequest,
    response
  );
  responseStream.destroy(err);
  reject(err);
});

responseStream.on('error', function handleStreamError(err) {
  if (req.destroyed) return;
  reject(AxiosError.from(err, null, config, lastRequest, response));
});

This removes the post-hoc assignment, sets status correctly, and drops the now-unnecessary if (response) guard (which is unreachable anyway, response is const-declared at line 839 in the same closure, before the listeners are attached).

2. Please add a regression test

This closes a long-standing issue (#6935), and there are no tests. At a minimum, one test per path:

  • aborted: server writes headers, then res.destroy() mid-body → assert err.response.status, err.response.headers, and err.status.
  • error: same setup with an emitted stream error → same assertions.

Asserting err.status in particular would have caught issue #1.

3. Scope clarification (optional, for the PR description)

This fix only applies to buffered responses. When responseType: 'stream', the adapter takes the early branch at line 847, and these listeners are never attached. Worth calling out so users with streamed responses don't expect the same behaviour. Also worth noting that err.response.data will be undefined on the aborted path since data is only assigned in the 'end' handler; only metadata (status/headers) survives, which matches the stated intent.

Once 1 and 2 are addressed, I think this is good to go. Patch-level change, no breaking concerns.

Thanks, addressed in 47d5c57.

Changes made:

  • Passed response into AxiosError / AxiosError.from(...) directly for both buffered response stream aborted and error paths, so err.status is normalized correctly.
  • Added regression coverage for both paths asserting err.response.status, err.response.headers, and err.status.

Validated locally:

  • npm run test:vitest:unit -- tests/unit/adapters/http.test.js
  • npx eslint lib/adapters/http.js
  • git diff --check

Scope note: this remains limited to buffered responses; responseType: 'stream' still takes the early stream branch. On the aborted path, only response metadata is available, so err.response.data remains unset.

@afurm afurm requested a review from jasonsaayman April 25, 2026 18:35
@jasonsaayman jasonsaayman merged commit cd0d448 into axios:v1.x Apr 28, 2026
29 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commit::fix The PR is related to a bugfix priority::medium A medium priority status::changes-requested A reviewer requested changes to the PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AxiosError.response is undefined when HTTP stream is aborted after headers are received (Node.js)

2 participants