Skip to content

fix: register OpenResponses runId in chatAbortControllers for chat.abort support#30737

Open
davidemanuelDEV wants to merge 2 commits intoopenclaw:mainfrom
davidemanuelDEV:fix/openresponses-abort-registration
Open

fix: register OpenResponses runId in chatAbortControllers for chat.abort support#30737
davidemanuelDEV wants to merge 2 commits intoopenclaw:mainfrom
davidemanuelDEV:fix/openresponses-abort-registration

Conversation

@davidemanuelDEV
Copy link
Copy Markdown
Contributor

Summary

OpenResponses HTTP API tasks (resp_xxx) were not registered in the Gateway's chatAbortControllers Map, causing chat.abort requests to silently return aborted: false while the task continued executing.

Root Cause

When a task is started via the OpenResponses /v1/responses endpoint, the generated responseId was never added to chatAbortControllers. The Gateway RPC WebSocket (chat.send) correctly registers run IDs, but the HTTP path was missing this registration.

Changes

  • openresponses-http.ts: Create an AbortController for each request, register it in chatAbortControllers with the responseId as key before running the agent command. Clean up on completion (finally block for non-streaming, requestFinalize/error/close handlers for streaming).
  • server-http.ts: Thread chatAbortControllers through createGatewayHttpServer options to the OpenResponses handler.
  • server-runtime-state.ts: Pass the existing chatAbortControllers Map to createGatewayHttpServer, moved declaration before the bind-host loop.

Testing

  • TypeScript compilation passes (no new errors)
  • Minimal, focused change — only adds registration/cleanup logic

Fixes #30558

…ort support

OpenResponses HTTP API tasks (resp_xxx) were not registered in the
Gateway's chatAbortControllers Map, causing chat.abort requests to
silently fail with aborted: false.

This patch:
- Creates an AbortController for each OpenResponses request and
  registers it in chatAbortControllers with the responseId as key
- Cleans up the registration on completion, error, client disconnect,
  and stream finalization
- Threads chatAbortControllers from server-runtime-state through
  server-http to the OpenResponses handler

Fixes openclaw#30558
@openclaw-barnacle openclaw-barnacle bot added gateway Gateway runtime size: XS labels Mar 1, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c6539d457

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +456 to +457
const abortController = new AbortController();
if (opts.chatAbortControllers) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Wire OpenResponses abort controller into agent command

This registers a new AbortController for each /v1/responses run, but the controller’s signal is never threaded into runResponsesAgentCommand/agentCommand (which supports abortSignal), so chat.abort can now report aborted: true while the underlying run continues to execute. In environments using OpenResponses plus chat.abort, this causes false-positive aborts and ongoing token/runtime usage even after a claimed cancellation.

Useful? React with 👍 / 👎.

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.

Good catch — wiring the abort signal through now.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 1, 2026

Greptile Summary

Registers OpenResponses HTTP requests in chatAbortControllers Map to enable chat.abort support. However, the implementation is incomplete — while the controller is registered and can be looked up by chat.abort, the abort signal is not passed to the agent command execution, so tasks will continue running even after abort is called.

Key changes:

  • Threads chatAbortControllers Map from runtime state through HTTP server to OpenResponses handler
  • Creates AbortController and registers it before starting agent command
  • Adds cleanup in all exit paths (success, error, close)

Critical issue:

  • The abortController.signal must be passed to runResponsesAgentCommand and eventually to agentCommand. Without this, calling controller.abort() has no effect on the running task.

Confidence Score: 1/5

  • This PR has a critical bug that prevents the core functionality from working
  • While the PR correctly registers abort controllers and threads them through the server infrastructure, it fails to pass the abort signal to the agent command execution. This means chat.abort requests will appear to succeed but won't actually stop the running task — the stated goal of the PR is not achieved.
  • Pay close attention to src/gateway/openresponses-http.ts — the abort signal must be wired through to the agent command

Last reviewed commit: 0c6539d

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +456 to +470
const abortController = new AbortController();
if (opts.chatAbortControllers) {
const now = Date.now();
const timeoutMs = 10 * 60_000; // 10 minute default
opts.chatAbortControllers.set(responseId, {
controller: abortController,
sessionId: responseId,
sessionKey,
startedAtMs: now,
expiresAtMs: resolveChatRunExpiresAtMs({ now, timeoutMs }),
});
}
const cleanupAbortController = () => {
opts.chatAbortControllers?.delete(responseId);
};
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.

abortController.signal must be passed to runResponsesAgentCommand

The AbortController is created and registered in chatAbortControllers, but the signal is never passed to the agent command execution. When chat.abort is called, it will call controller.abort(), but since the agent command doesn't have a reference to the signal, it won't actually stop executing.

Compare with the WebSocket path in server-methods/chat.ts:867 which passes abortSignal: abortController.signal to dispatchInboundMessage.

Fix by:

  1. Adding abortSignal?: AbortSignal parameter to runResponsesAgentCommand
  2. Passing it to the agentCommand call (line 261)
  3. Passing abortController.signal in both calls to runResponsesAgentCommand (lines 474 and 710)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/openresponses-http.ts
Line: 456-470

Comment:
`abortController.signal` must be passed to `runResponsesAgentCommand`

The `AbortController` is created and registered in `chatAbortControllers`, but the signal is never passed to the agent command execution. When `chat.abort` is called, it will call `controller.abort()`, but since the agent command doesn't have a reference to the signal, it won't actually stop executing.

Compare with the WebSocket path in `server-methods/chat.ts:867` which passes `abortSignal: abortController.signal` to `dispatchInboundMessage`.

Fix by:
1. Adding `abortSignal?: AbortSignal` parameter to `runResponsesAgentCommand`
2. Passing it to the `agentCommand` call (line 261)
3. Passing `abortController.signal` in both calls to `runResponsesAgentCommand` (lines 474 and 710)

How can I resolve this? If you propose a fix, please make it concise.

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.

Agreed, fixing — will pass the signal to runResponsesAgentCommand.

The AbortController was created and registered in the abort map but
its signal was never passed to runResponsesAgentCommand, making
chat.abort a no-op. Pass abortController.signal at both call sites
(non-streaming and streaming paths).

Addresses review comment on PR openclaw#30737.
@davidemanuelDEV
Copy link
Copy Markdown
Contributor Author

@greptile-apps Good catch on the abort signal flow!

Looking at the diff, I see we're passing abortSignal: params.abortSignal to agentCommand() in runResponsesAgentCommand.

Could you clarify: does the agentCommand function interface actually accept an abortSignal parameter in its options? If not, that would explain why the abort signal isn't reaching the execution layer.

If agentCommand doesn't support abortSignal yet, this PR would need to either:

  1. Add abort signal support to agentCommand first, or
  2. Hook into whatever abort mechanism agentCommand already uses

What's the current abort interface for agentCommand?

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 19, 2026

Yes, agentCommand does accept abortSignal in its options!

Looking at src/agents/command/types.ts, the AgentCommandOpts type includes:

abortSignal?: AbortSignal;

And in agent-command.ts, the signal flows through correctly:

  • For ACP sessions: passed to acpManager.runTurn() as signal: opts.abortSignal (line ~1068)
  • For embedded agent runs: passed to runEmbeddedPiAgent() as abortSignal: params.opts.abortSignal (line ~1406)

So the abort signal path in this PR is complete:

  1. Create AbortController → register in chatAbortControllers map
  2. Pass signal to agentCommand() via options
  3. agentCommand() forwards it to the underlying execution layer

The PR should work as intended for both ACP and embedded Pi agent executions.

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

Labels

gateway Gateway runtime size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OpenResponses HTTP API tasks cannot be aborted via `chat.abort

2 participants