Skip to content

Keep a service reply that a tool result was recorded after - #1164

Merged
edwin-zvs merged 1 commit into
mainfrom
fix-trailing-tool-result-reply
Aug 2, 2026
Merged

Keep a service reply that a tool result was recorded after#1164
edwin-zvs merged 1 commit into
mainfrom
fix-trailing-tool-result-reply

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

Follow-up to #1162. Found while verifying that PR against a live interactive codex service.

Problem

A service turn whose final answer is followed by a tool result reports no reply at all:

  • the poll endpoint (GET /svc/<name>/sessions/<id>) returns ready: true with reply: null
  • wait_for_final (headless services) blocks until its 30-minute timeout

…while the answer is sitting in the transcript the whole time.

latest_assistant_reply walks the transcript backwards and stops at the first tool event. That's correct for the case it was written for — narration preceding a tool call is commentary, not the answer. But it also stops on a tool result that trails the answer, and harnesses don't guarantee a tool's result is recorded before the assistant text it produced. Codex flushes the reply tool's result afterwards, so the walk hit the result immediately and never reached the answer:

tool_use     construct_service_reply
message      assistant "CHARLIE"
tool_result  call_MXn6...            <- walk stopped here, reply = None

That transcript is from a real session, not a constructed one.

Fix

Skip trailing tool results until collection has actually begun. Once some answer text is in hand, a tool event is again the boundary that ends it — so a turn that stops inside a tool call still reports no reply, rather than serving pre-tool narration as the answer.

SessionEvent::ToolResult { .. } if parts.is_empty() => {}

Scope

The interactive reply path (construct_service_replyrecord_reply → channel adapter) never went through this function, so Slack pushes were unaffected. What was affected:

caller symptom
HTTP channel poll endpoint ready: true, reply: null
wait_for_final, headless services blocks to the 30-minute timeout

Tests

Two new cases, both pinned to real transcript shapes:

  • reply_survives_a_tool_result_recorded_after_the_answer — the bug. Verified it fails on main (left: None, right: Some("CHARLIE")) and passes with the fix.
  • a_turn_that_ends_inside_a_tool_call_reports_no_reply — guards the boundary the skip must not cross.

cargo test --workspace: 51 test binaries, 0 failures.

Binary

Touches crates/daemon → the relevant binary is construct.

A service turn whose final answer was followed by a tool result reported no
reply at all. The poll endpoint returned `ready` with `reply: null`, and a
waiting caller blocked until its 30-minute timeout — while the answer sat in
the transcript the whole time.

`latest_assistant_reply` walks the transcript backwards and stops at the first
tool event, which is right for the case it was written for: narration that
precedes a tool call is commentary, not the answer. But it also stops on a
tool result that *trails* the answer, and harnesses do not guarantee a tool's
result is recorded before the assistant text it produced. Codex flushes the
reply tool's result afterwards, so the walk hit the result immediately and
never reached the answer:

    tool_use     construct_service_reply
    message      assistant "CHARLIE"
    tool_result  call_MXn6...            <- walk stopped here, reply = None

Skip trailing tool results until collection has actually begun. Once some
answer text is in hand, a tool event is again the boundary that ends it, so a
turn that stops inside a tool call still reports no reply rather than serving
pre-tool narration as the answer.

Observed on a live interactive codex service session; both shapes are pinned
by tests.
@edwin-zvs
edwin-zvs merged commit 8b586d4 into main Aug 2, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the fix-trailing-tool-result-reply branch August 2, 2026 14:11
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.

1 participant