three defects, one shape: the code has two names for "which session is this", and every guard checked the one no caller uses. `fable` is the FIRST entry in the claude-code catalogue, so it is what ModelSelector picks whenever it decides a conversation needs a default. On a key without fable entitlement that lands as a 429, after a real mid-session switch — the reported header read OPUSPLAN → FABLE. 1. the settings default was applied to RESUMED chats. the guard was `!initialSessionId`, and no chat caller passes that when resuming — the id arrives as `options.resumeSessionId`, and ChatPanelWrapper goes as far as `resume ? undefined : sessionKey`. so a user's default model was written over sessions that already had one. both names are checked now. 2. `hasStarted` was `!!preloadedMessages?.length`, which is FALSE for the first frames of a resumed chat — before its messages arrive. ModelSelector reads "not started" as permission to pick a default, so a reload reassigned live sessions during that window. a session id is known on the first render and a chat that has one is by definition not new. 3. harness lock. `crossesHarness` returned false when the current harness was unknown, which reads as "nothing crosses" and is the opposite of what is known — with no current harness, everything might. so a started session offered a switch to opencode while its model was still resolving, a switch that cannot work: it hands a `ses_…` id to `claude --resume` and dispatches to the wrong sidecar. unknown is not permission; the strip locks until the answer arrives. the settings themselves were never wrong — chat.defaultModel reads claude-code/opus on the affected server. nothing was ever reading it for the sessions that got reassigned.
15 KiB
15 KiB