fix: keep the picked model and effort across an abort - #1596
Merged
Conversation
`resetCurrentModeDefaults()` runs on abort and reverted `currentModel` / `currentEffort` to the session launch defaults. Since the app only sends `meta.model` / `meta.effort` when the user changes the picker, every turn after an abort silently ran the launch default while the picker still showed the user's choice. Both the Claude and the Codex loop had the same reset. Permission mode, system prompts and tool overrides still reset as before. Fixes slopus#1595 Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1595
Problem
Pick a non-default model in the picker, abort a turn, then send another message — the next turn runs the session's launch default (
opus) instead, and the picker keeps showing the model you picked. Same for reasoning effort (reverts tomedium). Nothing in the UI indicates the switch.Cause
resetCurrentModeDefaults()inpackages/happy-cli/src/claude/runClaude.tsis the loop'sonAbort. It resetcurrentModel = options.model ?? DEFAULT_CLAUDE_MODELandcurrentEffortlikewise.options.modelis undefined for app-driven sessions, so the model landed on the'opus'constant.The app sends
meta.model/meta.effortonly when the picker changes (the outgoingMessageModeMetacarries exactlypermissionMode,model,modelProviderId,effort— the other fields the reset touches are never sent by the app, so resetting those is a no-op). That makescurrentModel/currentEffortthe only record of the user's pick — resetting them desyncs the CLI from the picker until the user re-touches it.packages/happy-cli/src/codex/runCodex.tshad the identical reset and the identical bug.Fix
Drop the model and effort resets from both functions. Permission mode still resets — that's deliberate post-abort safety behavior (see the comment in
runCodex.ts); fallback model, system prompts and tool overrides also still reset.Proof it works (end-to-end)
Real local happy-server (PGlite) + real seeded account + the real built CLI (
happy claude --happy-starting-mode remote) under a sandboxHAPPY_HOME_DIR; the app side scripted over the same encrypted wire protocol (messages viaPOST /v3/sessions/:id/messages, abort via socket.iorpc-call→<sessionId>:abort). Scenario: message withmeta.model: claude-sonnet-4-6→ abort → message with no meta.Before (main):
Turn 2's SDK subprocess ran with
--model opus; its assistant stream reports"model": "claude-opus-4-8".After (this branch, identical scenario):
Turn 2 ran with
--model claude-sonnet-4-6; zero opus occurrences in the logs.Test
Added a unit regression test in
runClaude.test.tson the existingstartRemoteRunClaudeHarness: message withmeta.model+meta.effort→ fireonAbort→ message with empty meta → the queued mode still carries the original model and effort. Fails onmainwithmodel: 'opus', effort: 'medium'; passes with the fix.runCodexhas no comparable unit harness in this package, so the Codex half is covered by the parity of the change only.🤖 Generated with Claude Code