Skip to content

Fix Codex chat creation after its own model listing fails - #25754

Merged
brennanb2025 merged 5 commits into
mainfrom
brennanb2025/codex-acquisition-catalog-followup
Oct 6, 2026
Merged

brennanb2025 merged 5 commits into
mainfrom
brennanb2025/codex-acquisition-catalog-followup

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 13 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1318 $\color{#cf222e}{\Huge{\mathbf{−}}}$​444 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​874
Prod 17 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​385 $\color{#cf222e}{\Huge{\mathbf{−}}}$​161 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​224

ELI5

A Codex chat could show “Chat could not be started” and Retry after Codex had already opened its thread. Orca still waited for that chat's model list during startup, so a failed or stalled list made a usable chat look broken.

The chat now opens when its Codex thread is ready. A failed or stalled model list does not hold a message, Stop, a follow-up sent to a running turn, or a model, effort, or Fast choice.

With saved Fast mode and an already known exact Fast tier, the first message still runs Fast. If no exact tier is known yet, the first message runs Standard immediately; a later message runs Fast once the model list arrives. The saved Fast choice stays on.

What Changed

  • Startup restores saved and Codex-reported choices without requesting a model list, then starts one bounded listing on the chat's own Codex connection in the background.
  • Sending, stopping, and steering a chat no longer request or wait for a model list. The model picker waits for choices outside the chat's action queue.
  • A model, effort, or Fast choice is checked against the model list Orca already has. If no list has arrived yet (for example, a choice made while a first chat is starting), the choice is saved and used by the next message instead of being refused; Codex itself rejects a model it does not offer, and that message fails with Codex's reason while the chat stays usable.
  • On reopening a chat, a saved Fast choice is checked against the saved model the next message will use, not the model the thread last ran, so it is no longer switched off by mistake.
  • Each message reads the exact Fast tier from the shared model catalog when it is sent, so a tier learned by any listing for the same account applies.
  • Other agents keep their existing option restoration behavior. This changes no remote wire fields or platform-specific commands.

Why

A successful Codex thread start proves the chat is ready; model discovery is separate work. Waiting for discovery at startup or inside the chat's action queue made a usable chat fail, or made Stop, later messages, and model choices wait behind a stalled request.

Orca saves Fast as on or off. The exact tier value Codex expects comes from its model list and can differ by model, so when that value is not known yet Orca sends Standard explicitly rather than guess a paid tier or rely on a shorthand Codex marks for removal. Sending Standard explicitly also means a resumed thread does not silently keep a paid tier.

Linked Issue

Related: #23831, which is now on the main branch. That change prevents a separate probe from holding chat startup and protects a newer catalog answer from an older probe; this PR removes the remaining wait on the chat's own model list.

Visual Proof

Local hidden Mac app run with a stub Codex whose own model list is rejected or stalled.

Before (main): a new-tab Codex chat whose thread opened still shows "Chat could not be started." with Retry when its own model list is rejected.

MR-picker-outcome.png

After (this PR, same rejected list): the chat opens and answers, including after an idle period.

PR3-picker-outcome.png

After (this PR, model list stalled): a model picked with no list loaded is kept (GPT-5.6 Luna) and used by the next message; a long turn is stopped and a message sent after Stop answers right away.

PS2-picker-outcome.png

Testing

  • I manually tested these changes locally: hidden Mac app with a stub Codex, on this exact head.
  • Automated tests added and updated.

Manual rows on this head: rejected list (Resolve with AI and new-tab Codex both start and answer; still answers after 8+ idle minutes); stalled list (chat starts in under 2 s; a model pick is kept and used; steer, Stop, and a send after Stop do not wait on the list); saved Fast with a known tier (both turns Fast); saved Fast with the catalog deleted and the list stalled after a restart (first message Standard immediately, Fast again once a list arrives). On main, both entry points show "Chat could not be started." for the rejected and stalled rows. No real Codex CLI was used.

On macOS against current main, 219 tests passed across the 16 focused Vitest files this PR touches, plus the catalog store tests; scoped Node typecheck and the changed-code quality gate passed; CI is green. Linux, Windows, remote machines, and mobile were not tested; structured Codex chats run only on the local host, and the change adds no platform-specific path, shortcut, or wire field.

Review

Reviewed over five rounds; the final summary, fixes, and deferred follow-ups are in the PR comment.

Agent skill upstream boundary

  • Not applicable; this change copies no upstream skill material.

Checklist

  • This PR is focused on the chat's own model-list dependency.
  • I explained the user-facing before and after, the mechanism, and why it was chosen.
  • Before and after screenshots from a local stub run are attached.
  • Self-reviewed for correctness and performance.
  • Cross-platform, SSH, and compatibility impact considered.
  • Full lint, typecheck, test, and build suites; CI will cover the broader gates.

@brennanb2025
brennanb2025 force-pushed the brennanb2025/codex-acquisition-catalog-followup branch from aa8dea1 to 9dc8d61 Compare October 6, 2026 05:51
…rives

- A live model, effort or Fast pick with no stored model list is kept as the
  next turn's intent instead of refused; Codex judges the model on turn/start
  and Fast without a known tier still sends Standard.
- Restoring saved Fast checks the saved model the turn will send, not the
  resumed thread's old model; the thread's effort is only reused for its own
  model.
- turn/start reads the exact Fast tier from the account's stored catalog at
  send time, so a tier any writer stored applies; the per-session copy is gone.
- Revert the delivery-loop extraction; that file matches main again.
…ach tests

Merged main (#25159) requires agents on attach and an agent definition for catalog access; the PR's new acquisition attach tests and Fast-mode tests now pass them.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Problem. A Codex chat could show "Chat could not be started." with Retry even though Codex had already opened its thread. Orca waited for that chat's own model list while confirming it owned the chat, so a failed list made a working chat look broken and a stalled list held chat start until the request timed out. This hit both new-tab Codex chats and Resolve with AI.

What changes for the user. The chat opens as soon as Codex's thread is ready. A failed or stalled model list no longer holds a message, Stop, a follow-up sent into a running turn, or a model, effort, or Fast choice. A choice made before the model list arrives (for example while a first chat is starting) is saved and used by the next message instead of being refused; if Codex rejects that model, the message fails with Codex's reason and the chat stays usable.

Fast mode. With Fast saved and the exact Fast tier already known, the first message runs Fast. If the tier is not known yet, the first message runs Standard right away and later messages run Fast once the list arrives; the saved Fast choice stays on. This is a deliberate trade: Orca does not guess a paid tier Codex has not named for that model.

Fixed during review. Earlier review rounds removed every model-list wait from the chat's action queue: a send-time Fast lookup that could hold Stop and follow-ups, a picker read and option validation that could queue sends behind a stalled list, and a restore that overwrote the saved next-turn model. The final round fixed a refused choice made during chat launch, a reopened chat switching saved Fast off by checking the wrong model, a reopened chat reusing another model's effort, and made each message read the exact Fast tier from the shared catalog. It also reverted an unneeded code move and fixed two tests broken by a newer main change.

Deferred follow-ups (low impact). While a chat's model list is still loading, the model button stays disabled (up to about 30 s); typing and Send are never blocked, and this is existing behaviour that is now reachable because the chat opens. After a model list fails, nothing asks again until the next message starts, so the picker keeps Orca's built-in model list and saved Fast stays Standard for one extra message; the follow-up is to re-read when the picker opens after a failed read. A later list saying the saved model has no Fast can turn Fast off for the live chat while the saved choice stays on until the next start. A cold picker and the background refresh can send two bounded model-list requests. The picker's own listing has no overall time cap beyond per-page limits. Skipping the live model read at chat start is opted into per agent (Codex here), so a future agent could bring the startup wait back; making every agent start from the options it already knows is a follow-up.

Verified. Local hidden Mac app run with a stub Codex on exactly 2fb9d47: with the chat's own model list rejected, Resolve with AI and new-tab Codex both start and answer, and still answer after 8+ idle minutes; with the list stalled, the chat starts in under 2 s, a model pick is kept and used by the next message, and steer, Stop, and a send after Stop do not wait; saved Fast with a known tier runs Fast on both turns; saved Fast after a restart with the catalog deleted and the list stalled sends Standard immediately and returns to Fast once a list arrives. On main, the same rejected or stalled list shows the start failure for both entry points. Focused stub tests (219 across the 16 touched files, plus catalog store tests), scoped Node typecheck, and the changed-code quality gate pass. CI on 2fb9d47 is fully green, including all five test shards and cross-version wire compatibility; neither of main's two known failures (the file-explorer bounded-scan test, with a separate fix in progress on main, and cross-version wire / streamparser, #25796) appeared on this head.

Not verified. Real Codex CLI, remote machines, Linux, Windows, and mobile UI. Structured Codex chats run only on the local host, and this change adds no wire field or platform-specific path.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 6, 2026 13:38
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0201de7c-4aa6-4893-b084-4c19c1ff4794
📥 Commits

Reviewing files that changed from the base of the PR and between 2fb9d47 and 596a8fe.

📒 Files selected for processing (3)
  • src/main/codex/codex-structured-acquisition-notification.ts
  • src/main/codex/codex-structured-session-acquire.ts
  • src/main/codex/codex-structured-session-state.ts
💤 Files with no reviewable changes (1)
  • src/main/codex/codex-structured-session-state.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Codex model and Fast-mode option handling now uses stored catalog data and supports background catalog refresh. Catalog requests can use a shared deadline. Session adapters add prepared live option reads and acquisition-time option reporting. The session wire applies prepared results only when the child remains unchanged. Tests cover catalog refresh, Fast-tier dispatch, option selection, acquisition restoration, and live option reads.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 596a8

The catalog refresh does not appear to update a closed or replaced chat. The prepared-read concern still warrants owner follow-up, but the established risk is bounded.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 40 functions across 24 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: preventing Codex chat creation from failing when its model listing fails.
Description check ✅ Passed The description covers the problem, user-facing changes, rationale, linked issue, visual proof, testing, review, and checklist. The full lint, typecheck, test, and build suites remain for CI, and the …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9599d81e-db9b-44b6-a699-2cef0542e53b
📥 Commits

Reviewing files that changed from the base of the PR and between b752131 and 2fb9d47.

📒 Files selected for processing (30)
  • src/main/codex/codex-close-turn-end.test.ts
  • src/main/codex/codex-configured-launch-defaults.ts
  • src/main/codex/codex-requested-close-turn-timing.test.ts
  • src/main/codex/codex-structured-acquire-catalog.ts
  • src/main/codex/codex-structured-acquisition-notification.ts
  • src/main/codex/codex-structured-background-catalog.ts
  • src/main/codex/codex-structured-catalog-entry.ts
  • src/main/codex/codex-structured-fast-mode.test.ts
  • src/main/codex/codex-structured-fast-mode.ts
  • src/main/codex/codex-structured-model-catalog.ts
  • src/main/codex/codex-structured-session-acquire.ts
  • src/main/codex/codex-structured-session-adapter.test.ts
  • src/main/codex/codex-structured-session-adapter.ts
  • src/main/codex/codex-structured-session-close.test.ts
  • src/main/codex/codex-structured-session-options-catalog.test.ts
  • src/main/codex/codex-structured-session-options.test.ts
  • src/main/codex/codex-structured-session-options.ts
  • src/main/codex/codex-structured-session-prompts.test.ts
  • src/main/codex/codex-structured-session-state.ts
  • src/main/codex/codex-structured-turn-start.ts
  • src/main/codex/codex-structured-turn-steer.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-acquisition-options.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-acquisition.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter-router.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter-router.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-conversation-stop.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-options-read.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts
💤 Files with no reviewable changes (6)
  • src/main/codex/codex-structured-session-state.ts
  • src/main/codex/codex-structured-session-close.test.ts
  • src/main/codex/codex-close-turn-end.test.ts
  • src/main/codex/codex-structured-fast-mode.ts
  • src/main/codex/codex-requested-close-turn-timing.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment on lines +139 to +142
context.serialize(sessionId, async () => {
const child = (await context.openConversation(sessionId))?.child
return child === started.child ? apply() : null
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Map the second openConversation failure to a read refusal.

The first serialized section wraps openConversation failures with journalOpenReadRefusal. The apply section at Line 140 calls openConversation again without that wrapper. The conversation can fail to reopen while the prepared listing is pending. In that case the picker gets a raw error instead of an AgentSessionRefusalError. The raw error also skips the logging that journalOpenReadRefusal does. Use the same mapping in both sections.

🐛 Proposed fix
             context.serialize(sessionId, async () => {
-              const child = (await context.openConversation(sessionId))?.child
+              const reopened = await context.openConversation(sessionId).catch((error: unknown) => {
+                throw journalOpenReadRefusal(error, context.deps.logger, sessionId)
+              })
+              const child = reopened?.child
               return child === started.child ? apply() : null
             })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
context.serialize(sessionId, async () => {
const child = (await context.openConversation(sessionId))?.child
return child === started.child ? apply() : null
})
context.serialize(sessionId, async () => {
const reopened = await context.openConversation(sessionId).catch((error: unknown) => {
throw journalOpenReadRefusal(error, context.deps.logger, sessionId)
})
const child = reopened?.child
return child === started.child ? apply() : null
})

…quisition-catalog-followup

# Conflicts:
#	src/main/codex/codex-structured-session-acquire.ts
@brennanb2025
brennanb2025 merged commit 128d154 into main Oct 6, 2026
73 of 78 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