Skip to content

Gate TUI deliver on Codex instructions refresh - #541

Merged
TheGreatAxios merged 6 commits into
mainfrom
cl-6907-tui-fires-refreshcodexinstructions-un-awaited-request-prefix
Aug 23, 2026
Merged

Gate TUI deliver on Codex instructions refresh#541
TheGreatAxios merged 6 commits into
mainfrom
cl-6907-tui-fires-refreshcodexinstructions-un-awaited-request-prefix

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Collaborator

Problem

src/tui/runner.ts fired refreshCodexInstructions() fire-and-forget
(void refreshCodexInstructions().catch(...)), two network fetches deep
(src/auth/codex/instructions.ts), mutating the module-level instructions
singleton. codex-responses-adapter.ts places that singleton at the top of
every 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.ts already awaits the refresh before building its first
request; 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.

  • Moved the Codex/xAI profile detection above the delivery queue in
    runner.ts and captured the refresh as a held Promise<void>
    (codexInstructionsRefreshed), resolved immediately for non-Codex
    profiles.
  • enqueueAgentDeliver — the single choke point both live delivery paths
    (requestContinuation and the main input path) go through — now awaits
    that promise via a new optional ready param on
    deliverAgentMessage, before it checks for a fatal build error or calls
    deliverToLiveAgent.
  • The existing .catch fallback is preserved: a failed refresh still lets
    the 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:

  • delivery holds until ready settles, and the value observed at delivery
    is 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.
  • delivery proceeds immediately when ready is already settled (steady
    state after turn 1).

Gates

  • bunx prettier --check / bunx eslint clean on all touched files except
    runner.ts, which already fails prettier on main before this change
    (pre-existing, repo-wide, unrelated) — confirmed via git stash diff, and
    eslint produces the identical 16 pre-existing errors on runner.ts with
    or without this change.
  • bun run typecheck, bun run build pass.
  • bun test ./src ./tests ./evals: 5115 pass, 1 fail (lsp-availability.test.ts,
    environment-only — fails identically on main before this change, missing
    typescript-language-server in this checkout).

Fixes CL-6907

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.
@linear-code

linear-code Bot commented Aug 23, 2026

Copy link
Copy Markdown

CL-6907

@TheGreatAxios
TheGreatAxios merged commit 7a122ab into main Aug 23, 2026
5 checks passed
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