never store a model the picker cannot render

The selected model went blank after a turn: visible only by opening the dropdown.

`officerModel` was coming back as `claude-code` — the bare harness. That is `DEFAULT_MODEL`, used when the
client sends no model and the user has no default. It ROUTES fine (isClaudeModel is a prefix test, and
split('/')[1] is undefined so the CLI picks its own), but it names no catalogue entry, so Radix's
`SelectValue` matched no item and rendered nothing.

It fed itself. A blank picker sends no model, so the next turn fell back to the bare harness again and
overwrote the real value — it destroyed a correctly backfilled `claude-code/opus` within one turn. My own
write path did that, added this afternoon.

Two changes. The server refuses to record a model with no tier: a bare harness means "no explicit choice",
and the honest response is to leave the previous answer alone rather than overwrite it with a non-answer.
And the trigger no longer trusts that its value names an item — an unmatchable value renders as its own
string instead of as an empty control, which also covers a retired model or a vanished OpenCode provider.

Repaired the one corrupted row in place by deleting it, so the transcript family fallback answers for that
session until its next real turn; it now reports claude-code/opus again.
This commit is contained in:
2026-08-17 18:06:08 +00:00
parent 640a74bcd5
commit 773f0e1c8b
3 changed files with 50 additions and 1 deletions
+16
View File
@@ -362,6 +362,22 @@ function createMessageHandler(sessionId: string, model: string, resumeSessionId?
*/
const record = (): void => {
if (!transcriptId) return;
/*
Only ever record a model that NAMES A TIER.
`DEFAULT_MODEL` is the bare harness, `claude-code`, used when the client sends no model and the user has
no default. It routes correctly — `isClaudeModel` is a prefix test, and `split('/')[1]` yields undefined
so the CLI picks its own model — but it is not an id the catalogue offers, so storing it puts a value in
the picker that matches no option and renders as BLANK.
That failure feeds itself: a blank picker sends no model, so the next turn falls back to the bare
harness again and overwrites whatever real value was there. It destroyed a correctly backfilled
`claude-code/opus` within one turn.
A bare harness means "no explicit choice", and the honest response to that is to leave the previous
answer alone rather than to overwrite it with a non-answer.
*/
if (!effective.includes('/')) return;
void setChatSessionModel(transcriptId, effective).catch((err: unknown) => {
logger.warn('Could not record the session model', {
sessionId,