fix(gateway): propagate AbortController signal on HTTP client disconnect#54388
fix(gateway): propagate AbortController signal on HTTP client disconnect#54388obviyus merged 4 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR wires Confidence Score: 5/5Safe to merge — the change is strictly additive abort wiring with no behavioral impact on the happy path. The implementation is logically correct: the No files require special attention. Reviews (3): Last reviewed commit: "fix: use correct 'close' event name for ..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cfc88cdc57
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3434550b83
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ff718e5fb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
2ca816b to
2a653d3
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2a653d3262
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82941d8d1b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
6a5ef52 to
6b9d87b
Compare
|
Pushed a small follow-up directly to the PR branch. What changed:
Why:
Commit: |
Abort running agent commands when the HTTP client disconnects for both /v1/chat/completions and /v1/responses endpoints. - Listen on res "close" instead of req "close" (the request body is already consumed so IncomingMessage auto-destroys before we get here). - Non-streaming: guard with !signal.aborted so the abort fires on genuine disconnects; a spurious abort after sendJson is harmless. - Streaming: guard with !closed so normal res.end() completions do not abort post-turn work still in flight. - Skip error logging and response writes when the signal is already aborted. Made-with: Cursor
…equests Updated the event listener for client disconnects to use the correct name and enhanced error handling logic. The changes ensure that abort signals are properly checked before logging errors and returning responses, preventing unnecessary operations on aborted requests. Made-with: Cursor
732c33d to
7313421
Compare
obviyus
left a comment
There was a problem hiding this comment.
Reviewed latest changes; landing now.
|
Landed on main. Thanks @Lellansin. |
…thanks @Lellansin) * fix: abort in-flight HTTP requests on client disconnect Abort running agent commands when the HTTP client disconnects for both /v1/chat/completions and /v1/responses endpoints. - Listen on res "close" instead of req "close" (the request body is already consumed so IncomingMessage auto-destroys before we get here). - Non-streaming: guard with !signal.aborted so the abort fires on genuine disconnects; a spurious abort after sendJson is harmless. - Streaming: guard with !closed so normal res.end() completions do not abort post-turn work still in flight. - Skip error logging and response writes when the signal is already aborted. Made-with: Cursor * fix: correct event listener name and improve error handling in HTTP requests Updated the event listener for client disconnects to use the correct name and enhanced error handling logic. The changes ensure that abort signals are properly checked before logging errors and returning responses, preventing unnecessary operations on aborted requests. Made-with: Cursor * fix: use correct 'close' event name for non-streaming disconnect handler * fix: watch socket close for HTTP aborts --------- Co-authored-by: 冰森 <dingheng.huang@urbanic.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
…thanks @Lellansin) * fix: abort in-flight HTTP requests on client disconnect Abort running agent commands when the HTTP client disconnects for both /v1/chat/completions and /v1/responses endpoints. - Listen on res "close" instead of req "close" (the request body is already consumed so IncomingMessage auto-destroys before we get here). - Non-streaming: guard with !signal.aborted so the abort fires on genuine disconnects; a spurious abort after sendJson is harmless. - Streaming: guard with !closed so normal res.end() completions do not abort post-turn work still in flight. - Skip error logging and response writes when the signal is already aborted. Made-with: Cursor * fix: correct event listener name and improve error handling in HTTP requests Updated the event listener for client disconnects to use the correct name and enhanced error handling logic. The changes ensure that abort signals are properly checked before logging errors and returning responses, preventing unnecessary operations on aborted requests. Made-with: Cursor * fix: use correct 'close' event name for non-streaming disconnect handler * fix: watch socket close for HTTP aborts --------- Co-authored-by: 冰森 <dingheng.huang@urbanic.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
…thanks @Lellansin) * fix: abort in-flight HTTP requests on client disconnect Abort running agent commands when the HTTP client disconnects for both /v1/chat/completions and /v1/responses endpoints. - Listen on res "close" instead of req "close" (the request body is already consumed so IncomingMessage auto-destroys before we get here). - Non-streaming: guard with !signal.aborted so the abort fires on genuine disconnects; a spurious abort after sendJson is harmless. - Streaming: guard with !closed so normal res.end() completions do not abort post-turn work still in flight. - Skip error logging and response writes when the signal is already aborted. Made-with: Cursor * fix: correct event listener name and improve error handling in HTTP requests Updated the event listener for client disconnects to use the correct name and enhanced error handling logic. The changes ensure that abort signals are properly checked before logging errors and returning responses, preventing unnecessary operations on aborted requests. Made-with: Cursor * fix: use correct 'close' event name for non-streaming disconnect handler * fix: watch socket close for HTTP aborts --------- Co-authored-by: 冰森 <dingheng.huang@urbanic.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Summary
/v1/chat/completionsand/v1/responsesHTTP endpoints do not cancel the underlying agent execution when a client disconnects. The close handler only sets aclosedflag and unsubscribes from agent events, but never aborts the in-flight LLM call.AbortControllerin each handler (both streaming and non-streaming paths) and passabortController.signalthrough toagentCommandFromIngress. On client disconnect (res.on("close")), the controller is aborted, which propagates cancellation to the agent runtime via the existingabortSignalplumbing (acpManager.runTurn({ signal })). Usedres.on("close")instead ofreq.on("close")because the request body is fully consumed byhandleGatewayPostJsonEndpointbefore the abort listener is registered, andIncomingMessageauto-destroys after EOF in modern Node.js.AbortSignalinfrastructure was already in place; this PR only wires it into the two HTTP endpoints that were missing it.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
src/gateway/server-methods/chat.ts) already implements this pattern correctly; this PR brings HTTP endpoints to parity.Root Cause / Regression History (if applicable)
/v1/chat/completionsand/v1/responsesHTTP handlers were originally implemented withoutAbortControllerintegration. TheabortSignalparameter onAgentCommandOptsexisted but was never wired in from these HTTP code paths.server-methods/chat.ts:1367) was implemented with proper abort support from the start; the HTTP endpoints were added later without this consideration.Regression Test Plan (if applicable)
src/gateway/openai-http.test.ts,src/gateway/openresponses-http.test.tsabortSignalpassed toagentCommandFromIngressmust transition to aborted state.http.request+req.destroy()to simulate client disconnection. This isolates the exact behavior without requiring a full agent runtime.server.chat.gateway-server-chat-b.test.ts.User-visible / Behavior Changes
When an HTTP client disconnects from
/v1/chat/completionsor/v1/responses(streaming or non-streaming), the backend now cancels the in-flight LLM call instead of letting it run to completion. This reduces wasted tokens and frees resources faster.Security Impact (required)
Repro + Verification
Environment
Steps
/v1/chat/completionsor/v1/responses.Expected
Actual
Evidence
Local verification commands:
pnpm test -- src/gateway/openai-http.test.ts(6/6 passed)pnpm test -- src/gateway/openresponses-http.test.ts(14/14 passed)pnpm format(clean)Human Verification (required)
/v1/chat/completionsand/v1/responses; all pre-existing tests still pass.res.on("close")vsreq.on("close")— verified thatreq.on("close")does NOT fire reliably after body consumption in modern Node.js due toIncomingMessageauto-destroy; switched tores.on("close")which fires when the underlying TCP connection closes.pnpm testsuite run; live integration with real LLM provider.Review Conversations
Compatibility / Migration
Failure Recovery (if this breaks)
src/gateway/openai-http.ts,src/gateway/openresponses-http.ts.res.on("close")fires prematurely (before response is sent), it could abort requests that should complete. This would manifest as incomplete or missing responses.Risks and Mitigations
res.on("close")might fire after a successful response is fully sent, callingabortController.abort()on an already-completed operation.AbortControlleris a no-op. The agent command has already returned by that point, so the signal is harmless.