fix(openai-compat): require a live conversation id before sending the tool reminder - #76
Merged
Enderfga merged 1 commit intoAug 14, 2026
Conversation
… tool reminder The resume-turn reminder was gated on `!needsCreate`, which only says the session is in the manager's map. That is not the same as the engine having created a conversation: `start()` (base-oneshot-session.ts:103) resolves the cwd, assigns an id and marks the session ready without spawning anything, and the conversation id is captured later — codex on `thread.started` (persistent-codex-session.ts:220), agy by harvesting the log after the first turn. So a first send that fails before that point leaves the session in the map with no id, and the next turn takes the resumed-thread path: it sends a short reminder referring to tools the engine never received, with no schemas, no identity and no history — and still answers 200. The gate now also requires the id to be present, via a small exported predicate so the rule is testable on its own. Once a thread is live nothing changes. `SessionManagerLike.getStatus` had the two ids omitted from its hand-written return type; they are added as optional, which is what they are.
This was referenced Aug 7, 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.
The problem
schemasAlreadyInThreadgates the resume-turn reminder on!needsCreate, which only says the session is present in the manager's map. That is weaker than it looks:start()(base-oneshot-session.ts:103) resolves the cwd, assigns a session id, sets_isReadyand emitsready. It does not spawn anything.codexonthread.started(persistent-codex-session.ts:220),agyby harvesting the log after the first turn.So after a failed first send, the next turn sees
sessionExists = true, takes the resumed-thread path, and sendsbuildToolReminderBlock()— a reminder about tools the engine has never received. No schemas, no identity, no history. The request returns 200 with whatever the model makes of it.It is a quiet failure: nothing in the logs distinguishes it from a healthy resume, which is why it took a while to find in our deployment.
The change
Require the id to actually be there:
nativeThreadIsLive()is exported as a small pure predicate so the rule can be tested directly rather than throughhandleChatCompletion, which has no coverage today.Both failure directions are safe by construction: when in doubt it sends the full block, which is correct but larger, never the reminder-without-schemas.
SessionManagerLike.getStatusdeclared its return shape by hand and left both ids out; they are added as optional, which is what they are onSessionStats.Behaviour
perMessageMode, fresh session, one-shot enginesTests
Six cases on the predicate: id required for
codex/codex-app/agy, the two ids not interchangeable, and engines that hold context in a live process still treated as live soclaudedoes not regress.npm run build,npm run lint,npm run format:checkclean;vitest rungreen at 900/900 across 58 files.Why this one first
We run this in production on the
codexengine (~15 agents) and have two more changes queued that both need a trustworthy "is the thread really there" predicate — trimming already-sent tool results, and skipping the system prompt on resumed turns. Both are unsafe on top of!needsCreatealone, so this seemed like the right thing to send first and on its own.Separately filed #75, on
contextPercentand the auto-compaction gate — not bundled here since it is a different area.