--- id: known-bugs/the-model-picker-shows-the-bare-harness title: 'The model picker shows `claude-code` instead of the model that is running' theme: known-bugs status: open created: 2026-08-25 date_source: stated order: 5 --- **The model picker shows `claude-code` — the bare harness — instead of a model.** Reported by the owner 2026-08-25 with a screenshot: the composer's harness pill reads "Claude Code" (correct) and the model dropdown beside it reads `claude-code` (useless). He reports it happens often, both mid-turn and idle, and cannot reproduce it deliberately. What he wants is simple and worth quoting: **the dropdown should always show either the running model, or the model last used when the conversation finished.** **Where the raw string comes from.** `ModelSelector.tsx:329` deliberately prints the raw id when the effective model matches no catalogue entry — the alternative is a blank control, and the comment there already names `claude-code` as one of the ids that reaches it. So the picker is reporting honestly; the question is why it is being handed a harness with no tier. Three sources of a bare `claude-code`, all verified in the tree: 1. **`DEFAULT_MODEL` is `claude-code`** (`websocket.ts:48`), used when the client names no model and the user has no default (`websocket.ts:829`). 2. **Every adopted session** — `adoptOrphanedSession(ws, sessionId, DEFAULT_MODEL, '')` (`websocket.ts:1631`), the path a browser refresh takes when officer no longer holds the session. That record's model is then pinned for its life and sent to the client as `session:init.model`. Observed directly in the boot log after a `pm2 restart officer`: two of three adopted sessions logged `model=claude-code`, one `model=claude-code/sonnet`. 3. **It cannot self-heal.** `record()` refuses to store a model that names no tier (`websocket.ts:471`, and the comment there explains why: storing it would render blank and then feed itself). So a session with no `chat_session_models` row sends no model, the server re-derives the bare harness, and nothing ever writes a real one. Sixteen rows exist for a great many transcripts. **The event that would fix it already exists and is thrown away.** `model:resolved` carries exactly this answer — the harness's own resolved model, mapped through `officerModelForApiModel` — and the client already handles it as display-only, deliberately not touching `selectedModel` (`useChat.ts:645`, and the comment is right that it must not invent a choice). It is emitted `durable: false` (`turn-stream.ts:163`), so **a refresh never receives it**. That is the shape of the fix: on `attach`, tell the client the session's resolved model the same way, rather than leaving it on the bare default. Display-only, no choice invented, no new vocabulary. --- **One half fixed 2026-08-25: the dashboard chat panel was seeding the picker from the wrong id space.** `ChatPanelWrapper.tsx` passed `detail.model` where `/chat` and Command HQ both pass `detail.officerModel` — and both of those carry a long comment saying exactly why. `detail.model` is what the API served (`claude-fable-5`); the picker offers Officer ids (`claude-code/fable`). The server already resolves the right one, falling back to a family guess from the transcript when no row is stored (`chat.ts:248`), so the panel was discarding a correct answer and showing a raw API id. Worth more than the label: that value is **sent back**. `isClaudeModel` is `startsWith('claude-code')` (`websocket.ts:66`), so `claude-fable-5` fails it and the turn dispatches to **OpenCode**. The clamp above it only fires when officer still holds the session in memory (`websocket.ts:845`) — which is precisely not the case after a restart, the same condition that produces the bare harness. Not observed in the wild, and filed here rather than separately because it is the same one-line cause. **Still open:** sources 1–3 above. The reported screenshot was not reproduced to a single path — with `officerModel` seeding `selectedModel`, `/chat` and Command HQ should both survive an adopted session, which points at the panel or at a session whose transcript names no model at all. The `attach` fix stands on its own regardless of which one he hit.