a stored bare harness is not a model choice
CHAT_DEFAULT_MODEL appeared to do nothing, and the picker still read "claude-code" after a refresh. both because an account can have the BARE harness stored as its preference, and this one does: chat.defaultProjectModel and tasks.defaultModel are each 'claude-code'. the client selected it, sent it, and the server honoured a choice that means "whatever the CLI picks" — opus. the default only ever applied when nothing was stored, which was nobody. `claude-code` with no tier is a routing answer and a legitimate one — it is what decideResume assumes. as an answer to "which model", it is the absence of one. so it is now READ as unset, in all three places that ask: the chat's model resolution, the task resolver, and the client's initial selection. nobody's settings are rewritten. the row keeps what it says and "no tier chosen" now means what it always meant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -74,6 +74,20 @@ export const DEFAULT_MODEL = 'claude-code';
|
||||
*/
|
||||
export const CHAT_DEFAULT_MODEL = 'claude-code/sonnet';
|
||||
|
||||
/**
|
||||
* Is this stored preference actually a model, or just a harness?
|
||||
*
|
||||
* `claude-code` with no tier routes fine and is a legitimate value for the resume path — but as an answer
|
||||
* to "which model should this chat use" it means "whatever the CLI picks", which is Opus. Accounts have it
|
||||
* stored from before that distinction existed: on this machine `tasks.defaultModel` and
|
||||
* `chat.defaultProjectModel` were both bare, which is why the picker showed `claude-code` and the change
|
||||
* to `CHAT_DEFAULT_MODEL` appeared to do nothing.
|
||||
*
|
||||
* Rather than rewrite somebody's settings, a bare harness is READ as unset. The row keeps whatever it
|
||||
* says; the meaning of "no tier chosen" is now "use the default", which is what it always meant.
|
||||
*/
|
||||
export const isTierlessModel = (model: string | null | undefined): boolean => !model || !model.includes('/');
|
||||
|
||||
/**
|
||||
* What a commander runs on, regardless of the account's default.
|
||||
*
|
||||
@@ -871,7 +885,9 @@ async function handleChat(
|
||||
|
||||
// Provided model, then the account default, then Sonnet. NOT `DEFAULT_MODEL` — that is the bare harness
|
||||
// and means "whatever Claude Code picks", which is Opus. See `CHAT_DEFAULT_MODEL`.
|
||||
const requested = msg.model || (await getUserDefaultModel(userId)) || CHAT_DEFAULT_MODEL;
|
||||
const accountDefault = await getUserDefaultModel(userId);
|
||||
const chosen = msg.model || accountDefault;
|
||||
const requested = isTierlessModel(chosen) ? CHAT_DEFAULT_MODEL : chosen!;
|
||||
|
||||
/*
|
||||
A conversation may change TIER mid-flight; it may not change HARNESS.
|
||||
|
||||
@@ -28,7 +28,10 @@ async function resolveModel(userId: number): Promise<string> {
|
||||
try {
|
||||
const settings = await getUserSettings(userId);
|
||||
const tasks = settings?.tasks as Record<string, unknown> | undefined;
|
||||
return (tasks?.defaultModel as string) || DEFAULT_MODEL;
|
||||
// A bare `claude-code` is a harness, not a tier — read as unset. See `isTierlessModel` in
|
||||
// `api/chat/websocket.ts`; this machine had exactly that stored, so the default never applied.
|
||||
const stored = tasks?.defaultModel as string | undefined;
|
||||
return stored && stored.includes('/') ? stored : DEFAULT_MODEL;
|
||||
} catch {
|
||||
return DEFAULT_MODEL;
|
||||
}
|
||||
|
||||
@@ -173,8 +173,11 @@ export function useChat(initialSessionId?: string, initialModel?: string | null,
|
||||
|
||||
useEffect(() => {
|
||||
if (existingSessionId || initialModel) return;
|
||||
const defaultModel = (projectScoped && settings?.chat?.defaultProjectModel) || settings?.chat?.defaultModel;
|
||||
if (defaultModel) setSelectedModel(defaultModel);
|
||||
const stored = (projectScoped && settings?.chat?.defaultProjectModel) || settings?.chat?.defaultModel;
|
||||
// A stored value with no `/` is the bare harness — a routing answer, not a tier. Selecting it would
|
||||
// send it, and the server would then honour a "choice" that means "whatever the CLI picks", i.e. Opus.
|
||||
// Leaving it unselected lets the server apply CHAT_DEFAULT_MODEL, which is the point of that constant.
|
||||
if (stored && stored.includes('/')) setSelectedModel(stored);
|
||||
}, [existingSessionId, initialModel, settings, projectScoped]);
|
||||
|
||||
// Sync selectedModel when initialModel changes (e.g. resuming a session)
|
||||
|
||||
Reference in New Issue
Block a user