Skip to content

fix(sdk): always attach Response on HTTPError - #2668

Merged
Christian Bromann (christian-bromann) merged 1 commit into
langchain-ai:mainfrom
edenbuilds:fix/always-attach-error-response-2632
Aug 10, 2026
Merged

Christian Bromann (christian-bromann) merged 1 commit into
langchain-ai:mainfrom
edenbuilds:fix/always-attach-error-response-2632

Conversation

@edenbuilds

@edenbuilds edenbuilds commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Always pass includeResponse: true when converting failed Response objects to HTTPError, so error.response is available without registering onFailedResponseHook.

Test plan

  • New unit test should attach error.response without onFailedResponseHook passes with the fix
  • Same test fails with response: undefined when the fix is stashed (git stash A/B)
  • Changeset for @langchain/langgraph-sdk

Fixes #2632

@changeset-bot

changeset-bot Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 36c64db

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@langchain/langgraph-sdk Patch
@langchain/angular Patch
@langchain/react Patch
@langchain/svelte Patch
@langchain/vue Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@langchain/langgraph-checkpoint

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint@2668

@langchain/langgraph-checkpoint-mongodb

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-mongodb@2668

@langchain/langgraph-checkpoint-postgres

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-postgres@2668

@langchain/langgraph-checkpoint-redis

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-redis@2668

@langchain/langgraph-checkpoint-sqlite

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-sqlite@2668

@langchain/langgraph-checkpoint-validation

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-validation@2668

create-langgraph

npm i https://pkg.pr.new/langchain-ai/langgraphjs/create-langgraph@2668

@langchain/langgraph-api

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-api@2668

@langchain/langgraph-cli

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-cli@2668

@langchain/langgraph

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph@2668

@langchain/langgraph-cua

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-cua@2668

@langchain/langgraph-supervisor

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-supervisor@2668

@langchain/langgraph-swarm

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-swarm@2668

@langchain/langgraph-ui

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-ui@2668

@langchain/langgraph-sdk

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-sdk@2668

@langchain/angular

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/angular@2668

@langchain/react

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/react@2668

@langchain/svelte

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/svelte@2668

@langchain/vue

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/vue@2668

commit: 36c64db

@denyskorolkov

Matches the proposed fix in the issue (option 2 — always
attach response, decoupled from onFailedResponseHook).

Nit on the changeset: "even when onFailedResponseHook is unset" reads like the
hook is still relevant post-fix, but it plays no role anymore in whether
response gets attached - might read cleaner as just "Always attach the
underlying Response on HTTPError." (the historical coupling is useful context
in the PR description, doesn't need to be in the public changelog).

Also curious: is response still expected to be undefined in some paths
(e.g. a network failure with no Response object at all), or does this
guarantee a value now?

`AsyncCaller` attached the failed `Response` to `HTTPError` only when an
`onFailedResponseHook` was registered, so `error.response` was `undefined` for
everyone else. Inspecting a failure meant registering a hook whose real purpose
is retry side effects, and the coupling was invisible from the outside.

`includeResponse` is now always true. `HTTPError` is constructed in exactly one
place — the `isResponse(error)` branch — so every `HTTPError` the SDK throws
carries its `Response`. Failures that have no `Response` are unaffected: a
generic rejection is rethrown as-is and a connection failure is replaced by
`ConnectionError`, neither of which is an `HTTPError`, and both of which are
pinned by a test so the boundary stays explicit.

This is option 2 from the discussion on langchain-ai#2632 — attach unconditionally rather
than widen the hook.

17 SDK caller tests pass; the `error.response` regression fails against the
current `async_caller.ts`.

Fixes langchain-ai#2632
@edenbuilds
edenbuilds force-pushed the fix/always-attach-error-response-2632 branch from 134b4e8 to 36c64db Compare August 8, 2026 02:02
@edenbuilds

Copy link
Copy Markdown
Contributor Author

Thanks Denys Korolkov · Founder @ Routine Run (@denyskorolkov) — both taken.

Changeset. You're right, the hook has no role post-fix and mentioning it in the changelog only implies otherwise. Now just:

Always attach the underlying Response on HTTPError.

The historical coupling stays in the PR description.

On response still being undefined: not for HTTPError. It's constructed in exactly one place — the isResponse(error) branch of AsyncCaller.call — so with includeResponse: true every HTTPError the SDK throws now carries its Response.

A failure with no Response never becomes an HTTPError at all, which is the case you're pointing at:

  • a generic rejection hits if (error instanceof Error) throw error and is rethrown untouched;
  • a network failure whose message matches the connection checks (fetch failed, ECONNREFUSED, …) is replaced, once retries are exhausted, by a plain Error with name === "ConnectionError".

Neither carries response or status, and neither did before. So the property stays optional on the class because the constructor parameter is optional, but there's no longer an HTTPError path that omits it.

I added a test pinning that boundary, since it's exactly the question a future reader will have:

it("should not attach a response to failures that carry none", async () => {
  // TypeError("boom") → rethrown as-is; TypeError("fetch failed") → ConnectionError
  expect(error).not.toHaveProperty("response");
  expect(error).not.toHaveProperty("status");
});

Writing it corrected my own assumption, incidentally — I expected fetch failed to surface as the original TypeError, and it comes back as ConnectionError because the message matches the connection branch even with maxRetries: 0.

17 caller tests pass, and the error.response regression still fails against the current async_caller.ts. Squashed to one commit.

@denyskorolkov

Thanks Backend & AI Systems Engineer · Founder @ Prema Vision (Denys Korolkov · Founder @ Routine Run (@denyskorolkov)) — both taken.

Changeset. You're right, the hook has no role post-fix and mentioning it in the changelog only implies otherwise. Now just:

Always attach the underlying Response on HTTPError.

The historical coupling stays in the PR description.

On response still being undefined: not for HTTPError. It's constructed in exactly one place — the isResponse(error) branch of AsyncCaller.call — so with includeResponse: true every HTTPError the SDK throws now carries its Response.

A failure with no Response never becomes an HTTPError at all, which is the case you're pointing at:

  • a generic rejection hits if (error instanceof Error) throw error and is rethrown untouched;
  • a network failure whose message matches the connection checks (fetch failed, ECONNREFUSED, …) is replaced, once retries are exhausted, by a plain Error with name === "ConnectionError".

Neither carries response or status, and neither did before. So the property stays optional on the class because the constructor parameter is optional, but there's no longer an HTTPError path that omits it.

I added a test pinning that boundary, since it's exactly the question a future reader will have:

it("should not attach a response to failures that carry none", async () => {
  // TypeError("boom") → rethrown as-is; TypeError("fetch failed") → ConnectionError
  expect(error).not.toHaveProperty("response");
  expect(error).not.toHaveProperty("status");
});

Writing it corrected my own assumption, incidentally — I expected fetch failed to surface as the original TypeError, and it comes back as ConnectionError because the message matches the connection branch even with maxRetries: 0.

17 caller tests pass, and the error.response regression still fails against the current async_caller.ts. Squashed to one commit.

Thanks for doing this and sharing the ConnectionError catch, glad it's pinned in a test now. Contribution looks great.

@edenbuilds

Copy link
Copy Markdown
Contributor Author

Thanks Denys Korolkov · Founder @ Routine Run (@denyskorolkov) — appreciate the review and the green light. Happy to iterate if anything else comes up before merge.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you , LGTM 👍

@christian-bromann
Christian Bromann (christian-bromann) merged commit f9c0e88 into langchain-ai:main Aug 10, 2026
24 checks passed
Christian Bromann (christian-bromann) pushed a commit that referenced this pull request Aug 11, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @langchain/langgraph-sdk@1.9.29

### Patch Changes

- [#2668](#2668)
[`f9c0e88`](f9c0e88)
Thanks [@edenbuilds](https://github.com/edenbuilds)! - Always attach the
underlying Response on HTTPError.

- [#2675](#2675)
[`3958305`](3958305)
Thanks [@christian-bromann](https://github.com/christian-bromann)! -
fix(sdk): deliver input channel events on the root-bus fast path

`channelProjection` with `replay: false` (the `useChannelEffect`
default) compared `event.method` to channel names, so `input.requested`
never matched `"input"`. Match via `inferChannel` instead, same as the
slow path.

- [#2677](#2677)
[`4c0fd78`](4c0fd78)
Thanks [@christian-bromann](https://github.com/christian-bromann)! -
fix(sdk): defer stream join until lazy thread create commits

Hydrating an externally-minted thread id that 404s still opened
`/stream/events` before `POST /commands` created the row. On
langgraph_api's in-mem runtime that join is accepted but dead, so the
first run delivered nothing until idle reconnect. Treat missing threads
like client-minted ones and start the root pump only after dispatch
succeeds.

- [#2672](#2672)
[`5be518f`](5be518f)
Thanks [@christian-bromann](https://github.com/christian-bromann)! -
fix(sdk): surface nested interrupts on stream.interrupts

`input.requested` events from subgraphs/subagents were dropped live by a
root-only filter, while hydrate seeded them from `state.tasks`, so HITL
UIs saw nested interrupts only after reload. Mirror every namespace onto
`rootStore.interrupts` (with `Interrupt.namespace`), and resolve that
namespace in `respond({ interruptId })` when callers omit it.

- [#2676](#2676)
[`b3c1ceb`](b3c1ceb)
Thanks [@christian-bromann](https://github.com/christian-bromann)! -
fix(sdk): resolve respond() namespace from interrupt id

  When callers pass `{ interruptId }` without `namespace`, look the
  namespace up on `thread.interrupts` instead of defaulting to root.
## @langchain/angular@1.0.30

### Patch Changes

- [#2676](#2676)
[`b3c1ceb`](b3c1ceb)
Thanks [@christian-bromann](https://github.com/christian-bromann)! -
fix(sdk): resolve respond() namespace from interrupt id

  When callers pass `{ interruptId }` without `namespace`, look the
  namespace up on `thread.interrupts` instead of defaulting to root.

- Updated dependencies
[[`f9c0e88`](f9c0e88),
[`3958305`](3958305),
[`4c0fd78`](4c0fd78),
[`5be518f`](5be518f),
[`b3c1ceb`](b3c1ceb)]:
  - @langchain/langgraph-sdk@1.9.29
## @langchain/react@1.0.30

### Patch Changes

- [#2676](#2676)
[`b3c1ceb`](b3c1ceb)
Thanks [@christian-bromann](https://github.com/christian-bromann)! -
fix(sdk): resolve respond() namespace from interrupt id

  When callers pass `{ interruptId }` without `namespace`, look the
  namespace up on `thread.interrupts` instead of defaulting to root.

- Updated dependencies
[[`f9c0e88`](f9c0e88),
[`3958305`](3958305),
[`4c0fd78`](4c0fd78),
[`5be518f`](5be518f),
[`b3c1ceb`](b3c1ceb)]:
  - @langchain/langgraph-sdk@1.9.29
## @langchain/svelte@1.0.30

### Patch Changes

- [#2676](#2676)
[`b3c1ceb`](b3c1ceb)
Thanks [@christian-bromann](https://github.com/christian-bromann)! -
fix(sdk): resolve respond() namespace from interrupt id

  When callers pass `{ interruptId }` without `namespace`, look the
  namespace up on `thread.interrupts` instead of defaulting to root.

- Updated dependencies
[[`f9c0e88`](f9c0e88),
[`3958305`](3958305),
[`4c0fd78`](4c0fd78),
[`5be518f`](5be518f),
[`b3c1ceb`](b3c1ceb)]:
  - @langchain/langgraph-sdk@1.9.29
## @langchain/vue@1.0.30

### Patch Changes

- [#2676](#2676)
[`b3c1ceb`](b3c1ceb)
Thanks [@christian-bromann](https://github.com/christian-bromann)! -
fix(sdk): resolve respond() namespace from interrupt id

  When callers pass `{ interruptId }` without `namespace`, look the
  namespace up on `thread.interrupts` instead of defaulting to root.

- Updated dependencies
[[`f9c0e88`](f9c0e88),
[`3958305`](3958305),
[`4c0fd78`](4c0fd78),
[`5be518f`](5be518f),
[`b3c1ceb`](b3c1ceb)]:
  - @langchain/langgraph-sdk@1.9.29

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

error.response is only populated when callerOptions.onFailedResponseHook is configured

3 participants