app-server stream stalls after starting custom tool call with empty input

Open 💬 4 comments Opened Jul 12, 2026 by mcinteerhouse-ops
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex is running?

0.144.1

What platform is your computer?

Linux 6.17 arm64

What steps can reproduce the bug?

The failure is intermittent, observed through codex app-server --listen stdio:// using ChatGPT OAuth and gpt-5.6-sol with custom tools.

A typical affected turn:

  1. Start an app-server turn with a custom exec tool available.
  2. Allow several reasoning/tool cycles to complete normally.
  3. After a completed reasoning item, the Responses stream starts the next custom tool call but stops before emitting its input.

The Codex structured log ends with:

Output item item=CustomToolCall {
  id: Some("<redacted>"),
  status: Some("in_progress"),
  call_id: "<redacted>",
  name: "exec",
  namespace: None,
  input: ""
}

No tool-input delta, output_item_done, app-server item/completed, or terminal turn/completed follows. No HTTP/SSE/auth/socket WARN or ERROR is logged. The connection remains open until the embedding client aborts it after its idle timeout.

The custom tool is never dispatched, so this is not a hanging command or missing tool result.

What is the expected behavior?

The stream should either:

  • finish the custom-tool input and complete the item;
  • emit a terminal Responses/app-server error; or
  • detect/reconnect an idle partial stream without leaving the turn silently open.

What do you see instead?

Codex remains indefinitely between response.output_item.added for the custom tool call and its first/remaining input delta. The app-server never emits turn/completed, and the embedding client eventually reports a completion-idle timeout.

Observed locally:

  • 11 completion-idle timeouts across multiple agents/workloads since July 10.
  • Four deeply inspected failures show a partial custom-tool-call signature.
  • Three failures ended exactly at CustomToolCall(status=in_progress, input="").
  • Successful turns occur between failures, so authentication, model access, and the app-server process are generally healthy.

Controlled follow-up:

  • 0.143.0 cannot test GPT-5.6 because the backend returns The 'gpt-5.6-sol' model requires a newer version of Codex (same for Terra/Luna).
  • 0.144.1 completed 8/8 controlled turns containing 10 separate custom exec calls each (80/80 tool calls), confirming the problem is intermittent.

Additional information

The embedding client is OpenClaw 2026.6.11 over stdio. Its timeout projection reported zero active items because the partial custom-tool start had not reached an app-server completed-item notification. Codex's own logs_2.sqlite is what exposed the in-progress item.

The first observed failures began after installing Codex 0.144.1, but this is correlation rather than a confirmed regression boundary because GPT-5.6 is rejected by 0.143.0.

A useful diagnostic improvement would be logging Responses stream lifecycle events/deltas and explicit close/error reasons around partial custom-tool items.

View original on GitHub ↗

4 Comments

github-actions[bot] contributor · 1 month ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #>32366

Powered by Codex Action

keshavbotagent · 1 month ago

Additional reproduction on Codex CLI 0.144.6 (the latest npm release at the time of testing) confirms the same failure and adds evidence that the Responses stream can resume only after an embedding client has already timed it out.

Environment

  • codex app-server --listen stdio://
  • Linux x86_64
  • ChatGPT authentication
  • gpt-5.6-sol, xhigh
  • custom exec tool

All task, account, filesystem, thread, turn, call, and response identifiers are redacted.

Redacted timeline

  1. The turn completed 48 earlier tool calls, each with a matching result. No client-side tool execution was active at the failure point.
  2. The native stream emitted response.in_progress, then completed two reasoning items.
  3. At T+0, Codex logged:

``text
Output item item=CustomToolCall {
id: Some("<redacted>"),
status: Some("in_progress"),
call_id: "<redacted>",
name: "exec",
namespace: None,
input: ""
}
``

  1. The app-server client received no corresponding item/started, tool-input progress, error, or turn/completed. Its last visible notification remained the preceding item/completed, with zero active app-server requests and zero active turn items.
  2. After exactly 300,001 ms without an app-server notification, the embedding client sent turn/interrupt.
  3. 55 seconds after that interrupt, the Codex Responses log finally recorded:

``text
response.custom_tool_call_input.done
``

  1. The rollout ended as turn_aborted; no terminal assistant message or normal turn/completed was produced before cleanup.

This delayed .done event is useful evidence: the transport/model stream had not reached a terminal failure when the five-minute client watchdog fired. It was an in-flight partial custom-tool call that remained invisible to the app-server client for almost six minutes.

Context was not exhausted

The last token diagnostic reported:

estimated_token_count=190868
auto_compact_scope_limit=244800
full_context_window_limit_reached=false
token_limit_reached=false

The large context and xhigh reasoning may increase latency, but Codex did not classify this as a context-window or compaction failure.

Current source-path observation

I also checked the 0.144 release branch and current main:

  • codex-rs/codex-api/src/sse/responses.rs converts response.output_item.added and response.custom_tool_call_input.delta into internal ResponseEvent values.
  • codex-rs/core/src/session/turn.rs handles OutputItemAdded(CustomToolCall) by installing an optional argument-diff consumer, but it does not emit an app-server-visible ItemStarted for that partial custom-tool item.
  • ToolCallInputDelta only emits a core event when the selected tool has a diff consumer that returns an event; ordinary exec argument streaming therefore supplies no generic app-server liveness signal.
  • The raw response item notification is recorded only after the completed response item is persisted, which requires response.output_item.done.

That leaves app-server clients unable to distinguish a slow/stalled partial tool-call stream from a dead turn.

Suggested acceptance behavior

Any one of these would let embedding clients avoid premature mid-turn termination:

  • expose a generic app-server lifecycle/progress event for custom-tool output_item.added and input deltas;
  • enforce an internal Responses stream-idle timeout that emits a terminal turn error and allows a safe retry; or
  • provide an app-server liveness/status signal that confirms the active turn is still receiving partial model-stream progress.

The important contract is that a partial custom-tool call must not remain indefinitely active inside Codex while being completely invisible to app-server clients.

keshavbotagent · 1 month ago

Request for an invited contribution

Per the repository's contribution policy, I have not opened a pull request.

Would a Codex maintainer be willing to invite a focused PR for this issue? My proposed scope is:

  • expose an app-server v2 lifecycle/liveness signal when response.output_item.added starts a CustomToolCall, before the full input or output_item.done arrives;
  • keep that signal alive for later custom-tool input deltas using the existing or maintainer-preferred protocol surface, without exposing tool arguments unnecessarily;
  • leave tool execution, retry, and timeout policy unchanged; and
  • add an integration test whose SSE stream pauses between the partial custom-tool start and input completion, asserting that an app-server client observes progress before output_item.done. I would update the app-server schema/docs if the approved approach adds protocol surface.

If the team prefers an internal Responses stream-idle failure/retry boundary instead, I can implement that direction. Could a maintainer confirm whether this contribution is invited and which protocol shape is preferred?

anyech · 3 days ago

Fresh 0.149.1 / current-main update

This failure still reproduces with Codex CLI 0.149.1 and a custom tool over codex app-server --listen stdio://.

In a redacted affected turn:

  1. Several reasoning and tool cycles completed normally.
  2. At T+0, Codex core received response.output_item.added for CustomToolCall(status=in_progress, input="").
  3. No matching app-server notification, tool-input delta, output_item_done, tool dispatch, or turn/completed followed.
  4. At T+300.000s, the embedding client's post-tool completion watchdog interrupted the turn while reporting zero active app-server requests and zero active turn items.
  5. Codex's default 300-second stream-idle deadline, anchored to the provider frame, was 23 ms later. The outer watchdog therefore preempted Codex's own retry/error path.

The incomplete tool item was never persisted or executed.

Narrowed source seam

I rechecked current Codex main at d52478c:

  • ResponseEvent::OutputItemAdded(CustomToolCall) assigns an item ID and may install an argument-diff consumer;
  • it still does not project a nonterminal app-server lifecycle event for that active provider item; and
  • completion/persistence remains gated on OutputItemDone.

The exact raw evidence window contained one core OutputItemAdded record and zero app-server outgoing notifications. That rules out an embedding-client forwarding drop for this event and narrows the missing layer to Codex's core-to-app-server projection.

Tested implementation shape (not merged or released)

I validated an isolated implementation with this contract:

  • emit a transient rawResponseItem/started when OutputItemAdded starts a CustomToolCall or FunctionCall;
  • preserve and reuse a stable item ID, including when the provider omits the streamed item ID;
  • keep persistence, tool dispatch, and execution exclusively on OutputItemDone; and
  • route the new nonterminal notification through app-server protocol, MCP, and TUI consumers.

Focused validation passed:

  • app-server protocol tests: 291/291;
  • lifecycle integration regression: 1/1;
  • compile checks for core, app-server, app-server-protocol, MCP server, and TUI; and
  • regressions for fallback-ID reuse and dynamic-tool lifecycle: 1/1 each.

The discriminating client test keeps the completion watchdog paused while the provider item is active, while a no-start control still times out normally.

This does not treat partial input as executable, replay side effects, or synthesize terminal completion.

As temporary containment, an embedding client can set its post-tool watchdog above 300 seconds so Codex's own stream-idle retry/error path runs first. That only removes the deadline collision; it does not fix the invisible active-item lifecycle.

Per the repository contribution policy, I have not opened a pull request. The implementation and test packet are ready if maintainers want this exact protocol shape or invite a contribution.