Skip to content

fix: extract thread_id from Codex events for Session.send multi-turn#41

Merged
joshrotenberg merged 1 commit intomainfrom
fix/session-send-thread-id
Apr 10, 2026
Merged

fix: extract thread_id from Codex events for Session.send multi-turn#41
joshrotenberg merged 1 commit intomainfrom
fix/session-send-thread-id

Conversation

@joshrotenberg
Copy link
Copy Markdown
Collaborator

Summary

  • Fix Session.send/3 silently dropping conversation history on every follow-up turn against codex-cli ≥ 0.119. extract_session_id/1 was looking up "session_id" in events, but Codex emits its thread identifier as "thread_id" in the first thread.started event. The captured id was always nil, new_session_id fell through to the stored (nil) session.session_id, and the next call to Session.send/3 dispatched a fresh Exec (not ExecResume) with no memory of prior turns.
  • Change the lookup to prefer "thread_id", fall back to "session_id" for defensive compatibility with older Codex versions or forks.
  • Expose extract_session_id/1 with @doc false so the capture logic can be covered by unit tests (same pattern as Command.shell_cmd_args/3).

Closes #40.

Test plan

  • mix test — 222 tests pass (5 new regression tests for extract_session_id/1)
  • mix dialyzer — clean
  • Live multi-turn verification against codex-cli 0.119.0:
    --- Turn 1 ---
    session_id after turn 1: "019d79cf-c221-7702-924a-b10fd5a77b44"
    result1 stdout first line: {"type":"thread.started","thread_id":"019d79cf-c221-7702-924a-b10fd5a77b44"}
    
    --- Turn 2 ---
    result2 stdout: {"type":"thread.started","thread_id":"019d79cf-c221-7702-924a-b10fd5a77b44"}
    {"type":"turn.started"}
    {"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":"42"}}
    {"type":"turn.completed","usage":{"input_tokens":27980,"cached_input_tokens":18304,"output_tokens":33}}
    
    SUCCESS: turn 2 remembered the number 42 -> multi-turn via Session.send works
    
    Before this fix: session_id was nil after turn 1, and turn 2 ran as a fresh thread with no knowledge of the number.
    After this fix: session_id is a real thread id after turn 1, turn 2 emits the same thread id in its thread.started event (confirming Codex resumed the existing thread), and the assistant correctly answered "42".

Regression tests

New describe "extract_session_id/1" block in test/codex_wrapper/session_test.exs covers:

  • thread_id extracted from thread.started
  • session_id fallback when thread_id is absent
  • thread_id preferred when both are present
  • nil when neither is present
  • nil on an empty event list

Background

Discovered while filing #40 as a follow-up to the chore/add-dialyxir work — see the Session.stream/3 fix in #36. The two functions had the same underlying field-name bug, but only Session.stream/3 was visible to dialyzer because of the make_ref() type abuse. Session.send/3 compiled cleanly but failed at runtime without any indication.

Session.send/3 was silently broken for multi-turn against codex-cli
0.119+: extract_session_id/1 looked up "session_id" in events, but
Codex 0.119+ emits the thread identifier as "thread_id" in the first
`thread.started` event. The lookup always returned nil, new_session_id
fell through to the (nil) stored session_id, and every follow-up turn
ran as a fresh Exec (not ExecResume) with no memory of prior turns.

Change extract_session_id/1 to look for "thread_id" first, falling
back to "session_id" for defensive compatibility with older Codex
versions or forks. Expose the helper with @doc false so the
regression can be covered by unit tests (same pattern used by the
command.ex shell_cmd_args helper).

Added regression tests covering:
- thread_id extracted from thread.started
- session_id fallback when thread_id is absent
- thread_id preferred when both are present
- nil when neither is present
- nil on an empty event list

Live-verified against codex-cli 0.119.0: turn 1 captures thread_id
019d79cf-c221-7702-924a-b10fd5a77b44, turn 2 emits the same thread_id
in its thread.started event (confirming the thread was resumed), and
the assistant correctly remembers context from turn 1 ("what number
did I ask you to remember" -> "42").

Closes #40
@joshrotenberg joshrotenberg merged commit d43466d into main Apr 10, 2026
2 checks passed
This was referenced Apr 10, 2026
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.

Session.send/3 multi-turn broken on Codex 0.119+: looks for session_id, CLI emits thread_id

1 participant