Repository navigation
A chat's model, effort and Fast belong to the chat, not to a model - #30561
Conversation
… not to a model Structured chats kept their option values per model on the client, so a value the host saved without a model (a resumed chat that never picked one, Codex Fast saved on the conversation) had nowhere to live. The client either dropped it or labelled the chat with the listing's default model to hold it. - One flat record per chat holds model, effort, speed and Fast independently; a model pick changes only the model and the host reconciles the rest. - Controls come from the model they apply to, separately from the values; with no model known, a saved Codex speed still shows and Standard turns it off. - The options answer marks values a catalog default supplied (current.defaulted). A resumed chat never names or sends such a model as its own; a new chat still shows the default the host says it runs. An answer without the mark reads as before. - Every send, including a new chat's first message, carries the chat's own model and effort and held picks, never a default read off the picker. - A held launch pick supersedes only an earlier pick of the same option. Tracked in STA-10947.
…l it shows A new chat that never picked a model carries the default its picker names, as before, so it keeps the model it actually ran after the CLI's default changes. Only a resumed chat's defaulted model is never named or sent. Tracked in STA-10947.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ➖ Normal Merge Risk: 🔵 Low · up to
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
🟡 Minor · Update the stale structured-agent assertion. · structured-agent-model-catalog-first-frame.test.ts:135-142
src/main/runtime/structured-agent-model-catalog-first-frame.test.ts:135-142
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUpdate the stale structured-agent assertion.
StructuredAgentSessionOptionRecordno longer exposesmodeldirectly. The assertion must readstate.record.values.model. The current assertion is also vacuous at runtime becausestate.record.modelis not populated and therefore evaluates toundefined.Suggested fix
- expect(state.record.model).toBeUndefined() + expect(state.record.values.model).toBeUndefined()
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
fe754e83-e571-4056-ae2a-8e5576e06280
📒 Files selected for processing: 31
mobile/src/session/use-mobile-host-model-catalog-upgrade.tsmobile/src/session/use-mobile-structured-agent-options.tsmobile/src/session/use-mobile-structured-agent-session.tsmobile/src/session/use-mobile-structured-send-with-outcome.tssrc/main/claude/claude-structured-effort-default-at-rest.test.tssrc/main/claude/claude-structured-model-catalog.tssrc/main/claude/claude-structured-session-options.tssrc/main/codex/codex-structured-model-catalog.tssrc/main/codex/codex-structured-session-options.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-options-read.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-options-read.tssrc/main/native-chat/agent-session-wire/structured-agent-session-registered-definition.test.tssrc/main/runtime/structured-agent-new-chat-effort-at-rest.test.tssrc/renderer/src/components/native-chat/use-host-model-catalog-upgrade.tssrc/renderer/src/components/native-chat/use-structured-agent-session-option-state.tssrc/renderer/src/components/native-chat/use-structured-agent-session-send-options.tssrc/renderer/src/lib/structured-agent-session-launch-create-message.tssrc/renderer/src/lib/structured-agent-session-launch-options.test.tssrc/renderer/src/lib/structured-agent-session-launch-options.tssrc/shared/agent-session-wire.tssrc/shared/native-chat-session-option-snapshot.tssrc/shared/structured-agent-session-discovered-model.tssrc/shared/structured-agent-session-first-frame-picker.spec.tssrc/shared/structured-agent-session-option-snapshot.tssrc/shared/structured-agent-session-option-state.tssrc/shared/structured-agent-session-option-view.test.tssrc/shared/structured-agent-session-option-view.tssrc/shared/structured-agent-session-options.test.tssrc/shared/structured-agent-session-options.tssrc/shared/structured-agent-session-permission-option.tssrc/shared/structured-agent-session-unlisted-effort.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.
…listing names the default The flag lives only on a listed catalog, so the check confirms the catalog is not the unknown answer before reading it; the first-frame test reads the chat's flat model value.
…ds the model it now runs After a retired saved model is reset, the options answer no longer marks the model the child runs as a default, so the next send records it instead of replaying the refused model on every reopen.
… a model pick keeps only what the model offers A chat this view started names the default model the host fills in, whichever of the model list and the options answer lands first, and its first send carries the shown model and effort. A resumed chat still names and sends only its own values. A model pick, held or live, keeps an effort, speed or Fast only where the new model offers it, so a value the model cannot take is never shown, sent or remembered.
… host A live pick no longer drops values on the client; the host's reply, when it carries its full value set, replaces the chat's own values as a read does. Only a pick held before launch, with no host yet, keeps just what the picked model offers.
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
95244860-cbdf-41b8-81f1-6f6613ec865d
📒 Files selected for processing: 14
mobile/src/session/use-mobile-structured-agent-options.tssrc/main/claude/claude-structured-options.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-options-read.tssrc/main/runtime/claude-structured-retired-saved-model.test.tssrc/main/runtime/structured-agent-new-chat-effort-at-rest.test.tssrc/renderer/src/components/native-chat/use-structured-agent-session-option-state.tssrc/renderer/src/components/native-chat/use-structured-agent-session-options.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-options.tssrc/renderer/src/lib/structured-agent-session-launch-options.test.tssrc/renderer/src/lib/structured-agent-session-launch-options.tssrc/shared/structured-agent-session-option-view.test.tssrc/shared/structured-agent-session-option-view.tssrc/shared/structured-agent-session-options.test.tssrc/shared/structured-agent-session-options.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.
A pick's reply carries only the values the host set or was asked for, so it can't speak for the rest: it no longer removes values it leaves out. Joined tests pin real reply shapes and a new Claude chat in a fresh Claude home naming and pinning Opus High.
A Speed value another listed model offers but the named model doesn't is drawn as Standard, which is what the host reconciles it to, while the chat's record keeps the host's value. Effort, a tier no listed model offers, an unlisted model and a stand-in model's controls still show what the host holds. After a lost pick reply the picker no longer shows the old tier as a raw extra choice.
…a reopen The options answer no longer marks the default model and effort as stand-ins for a chat this host started that has sent nothing, so a chat reopened after a restart or on the phone names them and its first send pins them, as before. A chat taken from history, or one that has sent, keeps the mark.
…chat fixture's handle chain
…ers null for a missing record
A Codex tier the drawn model lacks shows as Standard, which is how Codex runs it, except Flex, which Codex passes regardless: established Flex keeps showing, and after a model pick the host hasn't answered it shows no selection until the reply or a read. Each Codex turn sends the same effective tier without rewriting the chat's saved one, and a new chat's create carries held picks as the picker draws them, settling them so they aren't applied again after publish.
…nt needs proof; create keeps held picks A Codex tier the model doesn't list shows and runs as Standard under one rule shared by the picker, the host's live reconciliation, each turn and the at-rest answer, which keeps the saved tier for a model that offers it. An older host's Flex shows as it reports it. A chat counts as never sent only when no turn, rewind or untrustworthy journal says otherwise. A new chat's create carries held picks as the picker draws them but keeps them as the user made them until create succeeds, so a refused create and retry still has the user's own pick, and a newer pick still applies after publish.
…option tests One typed resting-journal helper replaces six inline mocks and the broad object parameters the low-evidence check rejects. Cuts tests the joined flows already pin: the resumed list-or-answer ordering test, the restart-after-first-send view test, the held effort before a model pick, and the model-pick and restart loop of the saved-Fast test.
Drops the Codex Standard row (the joined tier test pins it), the dispatched-source and literal effort-choice checks, and the resumed view test restart and model-pick steps; folds the pick table.
… next start runs The reply to a pick made at rest now carries the tier the chat model runs, through the same rule as the at-rest answer, so an older app whose follow-up read fails shows what the next turn sends; the saved record keeps the pick. A saved tier no listed model offers (a Flex from the user Codex config) is the agent configured tier at rest too, as the live rule and the picker already read it. Restores the pin that an accepted Codex reply naming Standard shows with no later read.
Review status: done on
|
…ion-values # Conflicts: # src/main/claude/claude-context-usage-unloaded-turn.test.ts # src/main/codex/codex-structured-turn-start.ts # src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts # src/main/native-chat/agent-session-wire/structured-agent-session-registered-definition.test.ts # src/shared/agent-session-wire.ts
…lt at once A reopened chat that never saved a model showed just "Model" until a send started its agent, while main names the list default straight away. The picker now names the default the host marks for every chat, as main does. Whether a send carries that default is a separate rule: a new chat sends it, so its first send pins it; a reopened chat does not, so its next start still runs the agent's own.
…t turn ran A chat that never saved a model followed whatever the agent's default was at each start, so a chat resumed from history or a terminal could quietly switch models after a restart. When a message is handed to the agent on such a chat, the host now saves the model that turn ran: the message's own model when it carried one, otherwise the model the agent reports running. A default the host filled in, no model at all, or a model the agent would refuse as a choice on later sends, is never saved. The write sets the model only while none is saved, inside the record store's transaction, so a pick made meanwhile always wins and effort, speed and Fast are untouched. One host step covers Claude, Codex, ACP agents and Pi. No new saved field.
…fort its first turn ran A Codex message that left the model or effort unset was filled from the model list's defaults, not from what the running thread uses. Once the host saved the model a chat ran, the next Codex turn carried that model alone and ran the list's default effort for it (a configured xhigh became medium), and a message carrying only an effort ran the list's default model while the chat saved the thread's. A Codex message now resolves an unset model or effort to the thread's own, and only a switch to a different model takes that model's default effort, the same rule the other agents use. When a chat with no saved model records what its first turn ran, it now records the effort, service tier and Fast that turn ran too, in the same one-time store write: each only where the chat has none saved, only from the message's own value or the agent's real report, never a filled-in default, so a later change to the agent's configured effort no longer pairs the kept model with an effort it never ran. No new saved field and no wire change. A Codex follow-up sent while a chat's first turn runs is compared with what that turn actually runs, so it steers instead of waiting for a new turn just because the host saved the model meanwhile. Also fixes the delivery loop's optional recording hook type.
…and drop what a model pick can't run A Codex message that only switched the tier set the thread's effort as a lasting override, so a later switch to a model with no listed efforts was refused; the effort is again written only where the model brought one or an earlier override needs replacing. Recording what a turn ran now works per option: a chat that already saved its model still records the effort it ran, and so on for each option it has none saved for. Tier and Fast come only from the agent's own report, since a message steered into a running turn runs that turn's, and Fast is kept only when the agent reports it actually routed on: an off state may only mean Fast is unavailable. A model pick drops a saved effort, or Fast, that the newly picked model's listed row does not offer, in the one host step both resting and live picks go through, so no later send carries a choice the agent would refuse. The saved tier is kept, as before: it runs as the model's effective tier.
…the chat's model runs A chat with a saved model recorded the effort its agent's settings named, not the one the turn ran: with "effortLevel": "high" in Claude's settings and Haiku saved (no effort control), the chat saved high and every later send was refused. A Codex thread effort its saved model doesn't list did the same. What a turn ran now comes from the agent's own dispatch: Claude reports the choices it applied and the effort the CLI resolved for the model it applies, never the settings level alone; Codex reports its final request over the thread's own model and effort, and a steer reports its running turn. This evidence lives only in memory on the dispatch result; agents without it keep using their own reports. One admission rule now decides every saved option, inside the ordered record write: under a model the saved catalog lists, an effort must be one it offers and Fast must be one it replays (Claude needs positive support). A late record of a turn that ran on another model than the one the chat now has saves none of that turn's options. A model pick drops inherited options its new model doesn't run, an explicit pick or a new chat's option the model doesn't offer is refused with the existing option refusal, and unknown catalogs or unlisted models pass as before. The saved tier keeps its effective-tier rule, and nothing already saved is cleaned up later.
…inst the saved catalog too
…icks are judged inside the write The handover type was misnamed, which broke the typecheck. Claude and Codex now attach their dispatch evidence even when it names nothing, so a Claude turn dispatched before its settings readback never records the settings effort it did not run; the agent's own report still names the model and stands in only for agents without dispatch evidence. Recording leaves the saved-model and set-if-unset checks to the store's one admission rule. An explicit effort or Fast pick is now checked inside the ordered write, with the same refusal, and only at rest: a running chat's own menu can offer more than its saved catalog row (Pi's probe menu stops at high).
…r list its picker showed
…ad, resolved once by the create
…re are no attach options" This reverts commit e633d20.
…ument, not the arity
Since #30561 a Claude send resolves with the options it ran under; the stop test only needs the send admitted, and the options flow checks the ran options are exactly the per-message pick.
#31201) * test(claude): real-claude suite passes on any signed-in machine and current model aliases Model ids come from the CLI's own model list instead of dated ids, tests that need a SessionStart hook or an effort bring their own project settings, and the supervised-stop test launches and reads its transcript under one Claude home, as acquisition does. * test(claude): one real-claude flow for the model, effort and permission pickers and the context meter Drives the live CLI through the production send path (model list, set model, apply effort, read settings), checks the turn's context report, and relaunches into a new permission mode the way Orca changes it. CI only meets these requests through stand-ins. * test(claude): accept the ran options a send now reports Since #30561 a Claude send resolves with the options it ran under; the stop test only needs the send admitted, and the options flow checks the ran options are exactly the per-message pick. * test(claude): an alias pick's dispatch report may omit the model the CLI resolved
![Branch: after the CLI update and restart, the chat keeps opus[1m]](https://private-user-images.githubusercontent.com/79079362/678793451-755f1998-f8f4-4a8e-8e36-745f7fca521f.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3OTI2MjgwOTAsIm5iZiI6MTc5MjYyNzc5MCwicGF0aCI6Ii83OTA3OTM2Mi82Nzg3OTM0NTEtNzU1ZjE5OTgtZjhmNC00YThlLThlMzYtNzQ1ZjdmY2E1MjFmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjEwMjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYxMDIyVDAwMDk1MFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWM0MWM1YmMwNGIxZWUzODI5YTQyMDhkZmM3NDRiYjkwMTc1MzYzOGNkYTY1ZGM4YTQ5NTI1MWRlODQ1ZTJjMmQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRnBuZyJ9.OPkv3Dx4vWb39nN9ap3Uo4yfYRIhkzbjkeH8GYcjcj8)


![Starting point, 2.1.280: opus[1m] and High picked](https://private-user-images.githubusercontent.com/79079362/678793663-52ba9e44-9b39-4505-87e2-f307be842f65.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3OTI2MjgwOTAsIm5iZiI6MTc5MjYyNzc5MCwicGF0aCI6Ii83OTA3OTM2Mi82Nzg3OTM2NjMtNTJiYTllNDQtOWIzOS00NTA1LTg3ZTItZjMwN2JlODQyZjY1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjEwMjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYxMDIyVDAwMDk1MFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTllOTk5Mjk5YzAzY2ZiZGExYWIzZmQ0MTIyZDBjYjlkN2JhMWUwNDhiOGYxYzc5MTI1ZmUzMzhiY2E0YzVmYWYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRnBuZyJ9.ww4k3P7oHzpk59keaAPM474fgTueFgi4THysjTZAYis)










ELI5
A chat now keeps the model it actually runs. Each chat remembers its own model, effort and speed in one place, the same place the computer running the agent already keeps them, and a chat that never picked anything remembers what its first turn really ran instead of quietly following whatever the agent's default happens to be at each restart. Before, the app filed those choices under each model, so a choice that arrived without a model had nowhere to go, and a chat with no saved model could switch models on its own after the agent's default changed.
What Changed
The problem
A native chat's model, effort and speed controls were kept by the app per model, while the host that runs the chat (this computer, an SSH server or a paired server) keeps one set of choices per conversation. Two owners with two shapes for one fact, so any value that arrived without a model was dropped or hung on an invented model. And a chat that never saved a model (one picked up from a terminal or from Session History, one created before Orca started saving models, or any Codex chat where nobody touched the picker) had no model of its own at all, so it ran whatever the agent's default was at each start. What users saw:
What you see now
How it works
current.defaulted, naming the model or effort values that came from a fallback default rather than from the chat or the agent. The app names such a default on every chat but sends it only from a chat that has never sent anything (a new chat). When the field is missing, the answer came from an older server and the app behaves exactly as main.Why
The common pattern keeps a conversation's selection on the conversation, separate from the description of what each model supports, and stores the model, effort and speed a run used so the next run starts from them; a conversation never has "no model". This PR adopts that.
This reverses #27925's rule that "later starts without an explicit choice use the current account or folder default", for chats with no saved choices. The maintainer approved the reversal: a chat keeps what it ran, which is what users expect and what the common pattern does.
Alternatives considered and rejected:
Differences from the common pattern
Deferred / follow-ups
Linked Issue
Tracked in STA-10947. Follow-up tracked in STA-11271. #30107 keeps its model-list work and is updated on top of this after it lands. Builds on #30022 and #30436.
Visual Proof
Screenshots below are from real-app QA of an earlier head (
8886f2f) against main, with real signed-in agents in dedicated QA homes. They predate the recording change; QA on the current head is pending (see Testing).New Claude chat before the first Send (both sent
--model opus --effort highand kept Opus High after restart)Claude chats after an app restart (this branch; main passed the same checks)
Claude chat on a model the command-line tool stopped listing (
opus[1m]): its own row stays selected, effort is read-only, and Send passes the model literallyCodex speed and other agents
Testing
QA: real Electron QA on the 1c2e087 build (shipped code identical to the current head) passed the main flows against main: chats keep the model and effort they ran across restart and a CLI version swap, Codex keeps its model and effort after a config change, Haiku with a high settings effort and model picks no longer refuse sends, recorded Fast carries over, every agent re-applies its own model, new chats pin their default, no layout shift. Pi xhigh and real Grok/OpenCode sends in a folder were only partly covered. The original STA-10947 case also passed on the current head: an opus[1m] chat kept opus[1m] and High after a Claude CLI update that stopped listing it and a restart, while main relabelled it to Opus and silently ran plain Opus. Screenshots are in the review status comment.
Main paths QA on the current head must cover: a reopened Claude chat with no saved model names its default, Sends once, and after a restart shows and sends the model it ran; the same for a Codex chat with a configured effort, which keeps that effort after the config changes; an ACP agent (Grok or OpenCode) re-applies the model it reported; a new chat still pins the default it shows; a model pick drops an effort the new model doesn't offer; a Claude chat on Haiku with an effort in the settings file records no effort and keeps sending; a pick the model doesn't offer is refused with the existing message; the Claude launch command line after a recorded Fast.
Earlier real-app QA on
8886f2f(before the main merge and the recording change) passed: new Claude chat default shown, pinned on first Send and kept after restart; never-sent chat still names its default after restart; a chat cleared with /clear keeps its model; a retired Claude model keeps its own row; Codex Flex from Codex's config is sent as Flex; Codex keeps Fast across a startup retry; Grok and OpenCode pickers; no lag from picks. Blocked or not run there: a completed Flex reply (no provider capacity), Codex rewind, Priority to a model without Priority, a refused-then-retried create, older and newer version pairs, and the phone.Unit tests, run as named files. On the current head, 31 named files (414 tests) pass, plus two further runs of 13 files (205 tests) and 11 files (154 tests) on the admission commits. Files run on the commits since that QA include:
src/main/native-chat/agent-session-wire/structured-agent-session-message-choices.test.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-startup-attempt-host.test.ts,src/main/native-chat/agent-session-wire/structured-agent-session-send-options.test.ts,src/main/native-chat/agent-session-wire/structured-agent-session-permission-mode.test.ts,src/main/native-chat/agent-session-wire/structured-agent-session-host.test.ts,src/main/native-chat/agent-session-wire/structured-conversation-compaction.test.ts,src/main/codex/codex-structured-message-options.test.ts,src/main/codex/codex-structured-dispatch-boundary.test.ts,src/main/codex/codex-structured-turn-steer.test.ts,src/main/native-chat/agent-session-wire/structured-agent-session-operation-start-refusal.test.ts,src/main/codex/codex-structured-service-tier-shown.test.ts,src/main/codex/codex-structured-session-options.test.ts,src/main/claude/claude-structured-message-options.test.ts,src/main/claude/claude-structured-effort-default-at-rest.test.ts,src/main/claude/claude-structured-options.test.ts,src/main/claude/claude-context-usage-unloaded-turn.test.ts,src/main/runtime/structured-agent-session-pi-startup.test.ts,src/main/runtime/structured-agent-session-runtime-provider-started.test.ts,src/main/runtime/structured-agent-session-codex-scripted-child.test.ts,src/main/runtime/claude-structured-retired-saved-model.test.ts,src/main/runtime/structured-agent-new-chat-effort-at-rest.test.ts,src/main/runtime/structured-agent-model-catalog-first-frame.test.ts. Earlier rounds also ransrc/shared/structured-agent-session-options.test.ts,src/shared/structured-agent-session-option-view.test.ts,src/shared/structured-agent-session-first-frame-picker.spec.ts,src/renderer/src/lib/structured-agent-session-launch-options.test.ts,src/renderer/src/lib/structured-agent-session-launch-create-message.test.ts,src/renderer/src/components/native-chat/use-structured-agent-session-options.test.tsx,tests/e2e/claude-auto-mode-host-catalog.unit.test.tsandmobile/src/session/use-mobile-structured-agent-options.test.ts.The new tests follow whole flows, including: options are saved only under the model the chat runs, whichever of a pick and a turn's recording lands first, with no value crossing to another model even when both models offer it, and a value kept after the model list later drops it; Claude records the effort the command-line tool applied rather than the settings file's (including Haiku, where none is applied); a Codex thread effort the model doesn't list is resolved before the request is sent and the request is what gets recorded, and a follow-up steered into a running turn records that turn's values only; a Codex chat records the model and effort it ran (configured xhigh), its config then moves to another model and effort, and the next Send still carries the recorded pair; a Claude start that names only a filled-in default records nothing until the agent's settings report the real model, then records that; a Codex message that sets only an effort keeps the thread's model; Codex Fast, then Standard, then a model pick, then a restart; a speed the new model doesn't offer showing and running as Standard; a resumed chat never sending a default; a new chat pinning the default it shows after a restart and after a refused-then-retried create; both arrival orders of the options answer and the model list; a phone pick whose reply is lost; and an older server's answer behaving as main.
CI: to be confirmed on the current head.
AI Disclosure
Written with Claude Code. Reviewed over several rounds by automated reviewers, with real-app QA driven through the app's developer tools protocol.
Review
Multi-round review completed on
8886f2f. A narrow re-review then covered the main merge and every commit since (recording what ran, the per-option admission check and their fixes) and is done on that narrow scope: every finding is fixed on the current head (3631abf) or labelled above. QA is pending on the current head. The review status comment will be refreshed for the current head.Agent skill upstream boundary
Notes
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)Tracked in STA-10947.
Author: Brennan Benson (X: @BrennanKB5)