the chat panel seeded the model picker from the wrong id space

/chat and Command HQ both pass detail.officerModel into the picker, each with a
long comment saying why. ChatPanelWrapper passed detail.model — what the API
served (claude-fable-5) rather than what the picker offers (claude-code/fable).
The server already resolves the right one, falling back to a family guess from
the transcript, so the panel was discarding a correct answer to show a raw id.

It is sent back, too. isClaudeModel is startsWith('claude-code'), so
claude-fable-5 fails it and the turn dispatches to OpenCode. The clamp that
would catch it only fires while officer still holds the session in memory,
which is exactly not the case after a restart.

This is one half of the reported "the dropdown just says claude-code". The
other half — DEFAULT_MODEL being the bare harness, every adopted session
carrying it, and record() rightly refusing to store it so nothing self-heals —
is written up in todo/known-bugs/the-model-picker-shows-the-bare-harness.md
with the fix it needs: model:resolved already carries the answer and is already
display-only on the client, but it is durable:false, so a refresh never sees it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: f7a62308-0977-4cf4-aa29-c020853fd420
This commit is contained in:
2026-08-25 06:52:39 +00:00
co-authored by Claude Opus 5
parent f07a1c8cc7
commit 92841efac9
3 changed files with 73 additions and 4 deletions
@@ -255,7 +255,14 @@ export const ChatPanelWrapper = ({ panelId, promptPrefix }: ChatPanelWrapperProp
if (cancelled) return;
setResume({
id: selectedSessionId,
model: detail.model,
// `officerModel`, not `detail.model` — the same rule the /chat screen and Command HQ both state
// at length, and the only one of the three call sites that had it wrong. `detail.model` is what
// the API served (`claude-fable-5`); the picker offers Officer ids (`claude-code/fable`). Seeding
// the picker from the wrong id space showed the raw API string in the trigger, and then SENT it:
// `isClaudeModel` is `startsWith('claude-code')`, so `claude-fable-5` fails it and the turn
// dispatches to OpenCode — caught only when officer still holds the session in memory, because
// that is the one case where the server's own record overrules the socket.
model: detail.officerModel ?? null,
messages: detail.messages as unknown as ChatMessage[],
total: detail.total,
offset: detail.offset,