fix: extract thread_id from Codex events for Session.send multi-turn#41
Merged
joshrotenberg merged 1 commit intomainfrom Apr 10, 2026
Merged
fix: extract thread_id from Codex events for Session.send multi-turn#41joshrotenberg merged 1 commit intomainfrom
joshrotenberg merged 1 commit intomainfrom
Conversation
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
This was referenced Apr 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Session.send/3silently dropping conversation history on every follow-up turn againstcodex-cli ≥ 0.119.extract_session_id/1was looking up"session_id"in events, but Codex emits its thread identifier as"thread_id"in the firstthread.startedevent. The captured id was alwaysnil,new_session_idfell through to the stored (nil)session.session_id, and the next call toSession.send/3dispatched a freshExec(notExecResume) with no memory of prior turns."thread_id", fall back to"session_id"for defensive compatibility with older Codex versions or forks.extract_session_id/1with@doc falseso the capture logic can be covered by unit tests (same pattern asCommand.shell_cmd_args/3).Closes #40.
Test plan
mix test— 222 tests pass (5 new regression tests forextract_session_id/1)mix dialyzer— cleancodex-cli 0.119.0:session_idwasnilafter turn 1, and turn 2 ran as a fresh thread with no knowledge of the number.After this fix:
session_idis a real thread id after turn 1, turn 2 emits the same thread id in itsthread.startedevent (confirming Codex resumed the existing thread), and the assistant correctly answered "42".Regression tests
New
describe "extract_session_id/1"block intest/codex_wrapper/session_test.exscovers:thread_idextracted fromthread.startedsession_idfallback whenthread_idis absentthread_idpreferred when both are presentnilwhen neither is presentnilon an empty event listBackground
Discovered while filing #40 as a follow-up to the
chore/add-dialyxirwork — see theSession.stream/3fix in #36. The two functions had the same underlying field-name bug, but onlySession.stream/3was visible to dialyzer because of themake_ref()type abuse.Session.send/3compiled cleanly but failed at runtime without any indication.