Gate TUI deliver on Codex instructions refresh - #541
Merged
TheGreatAxios merged 6 commits intoAug 23, 2026
Merged
Conversation
The TUI fired refreshCodexInstructions() without awaiting it, so the in-memory instructions singleton could swap mid-session after inference #1 started. Since codex-responses-adapter puts the instructions at the top of every request body, that swap changes turn #2's whole prefix and forces a guaranteed prompt-cache miss at a nondeterministic point. Hold the refresh promise and gate every queued deliver on it instead of blocking TUI startup: enqueueAgentDeliver now awaits it via deliverAgentMessage's new ready param before building the first request, matching the await in src/exec/runner.ts. The existing .catch fallback is unchanged, so a failed refresh still leaves the session on cached/bundled instructions rather than crashing.
…n-awaited-request-prefix Amp-Thread-ID: https://ampcode.com/threads/T-01a02c68-0d8d-777b-b717-81fb9a282023 Co-authored-by: Amp <amp@ampcode.com>
…n-awaited-request-prefix
…n-awaited-request-prefix
…n-awaited-request-prefix
…n-awaited-request-prefix
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.
Problem
src/tui/runner.tsfiredrefreshCodexInstructions()fire-and-forget(
void refreshCodexInstructions().catch(...)), two network fetches deep(
src/auth/codex/instructions.ts), mutating the module-level instructionssingleton.
codex-responses-adapter.tsplaces that singleton at the top ofevery request body. When the refresh resolved after inference #1 started,
turn #2's entire request prefix changed underneath it, guaranteeing one
full provider prompt-cache miss per session at a nondeterministic point.
src/exec/runner.tsalready awaits the refresh before building its firstrequest; only the TUI raced it.
Fix
Chose "gate only the first request on the promise settling" over blocking
TUI startup on a network fetch, since the TUI has other useful startup work
(menus, banners, etc.) that shouldn't wait on a Codex-specific refresh, and
non-Codex profiles shouldn't pay for it at all.
runner.tsand captured the refresh as a heldPromise<void>(
codexInstructionsRefreshed), resolved immediately for non-Codexprofiles.
enqueueAgentDeliver— the single choke point both live delivery paths(
requestContinuationand the main input path) go through — now awaitsthat promise via a new optional
readyparam ondeliverAgentMessage, before it checks for a fatal build error or callsdeliverToLiveAgent..catchfallback is preserved: a failed refresh still letsthe session proceed on cached/bundled instructions instead of throwing.
Because every deliver — not just the first — awaits the same already-settled
promise, later turns pay no cost, and the first request can never observe a
mid-flight swap of the instructions value.
Test
Added two cases to
deliver-agent-message.test.ts:readysettles, and the value observed at deliveryis the post-refresh one — i.e. the swap happens before the first request
is built, not after — matching the "first request waits for it" behavior
chosen here.
readyis already settled (steadystate after turn 1).
Gates
bunx prettier --check/bunx eslintclean on all touched files exceptrunner.ts, which already fails prettier onmainbefore this change(pre-existing, repo-wide, unrelated) — confirmed via
git stashdiff, andeslint produces the identical 16 pre-existing errors on
runner.tswithor without this change.
bun run typecheck,bun run buildpass.bun test ./src ./tests ./evals: 5115 pass, 1 fail (lsp-availability.test.ts,environment-only — fails identically on
mainbefore this change, missingtypescript-language-serverin this checkout).Fixes CL-6907