ClaudeSession: --model flag + restart-based switch with --resume (closes #491)#492
Merged
Conversation
…loses #491) Claude in stream-json mode does not accept the /model slash command — it echoes "Unknown command: /model" to stdout and never emits a turn boundary. ClaudeSession.switch_model was sending /model and then draining iter_events, so the first switch_model call hung forever. When set_status held the global status_update lock, every other worker's set_status blocked on the lock — kennel deadlocked. Research confirms (undocumented, verified against claude 2.1.107): - Model is settable via the ``--model`` CLI flag at spawn time only. - No stream-json control_request subtype for mid-session model change. - Conversation context survives across subprocess swaps via ``--resume <session_id>``, recorded in stream-json ``session_id`` fields on events. Changes: - ClaudeSession gains a ``model`` kwarg (default claude-opus-4-6) and passes ``--model`` to the subprocess at spawn. - iter_events captures the latest ``session_id`` from stream-json events into self._session_id. - switch_model is now restart-based: if the target model differs, hold the session lock for the full swap, kill the old proc, spawn a new one with ``--model <new> --resume <sid>`` so conversation context carries over. Threads waiting on ``with session:`` block on the session lock until the new pid is ready — graceful handoff. - restart() (used on issue boundaries to bound context growth) now also holds the session lock for the swap, and clears session_id first so the new spawn starts fresh conversation — the opposite of switch_model's context-preserving behavior.
rhencke
approved these changes
Apr 14, 2026
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.
Stream-json doesn't accept the
/modelslash command.ClaudeSession.switch_modelwas sending/model <name>over stdin then drainingiter_events— claude echoed "Unknown command: /model" and never emitted a turn boundary. Firstswitch_modelcall hung forever. Compounded:set_statusholds the globalstatus_updatelock for its whole flow, so once one worker'sset_statuswedged on emoji generation (via the session), every other worker'sset_statusblocked. Kennel deadlocked.Fix
Research (see #491 thread): no stream-json control_request for model switching exists;
--modelat spawn time +--resume <session_id>across swaps is the only path.ClaudeSessiongains amodelkwarg (defaultclaude-opus-4-6), threaded into_spawnas--model.iter_eventsnow captures the latestsession_idfrom stream-json events.switch_model: no-op when model matches current; otherwise holds_lockfor the full swap, kills the old proc, respawns with--model <new> --resume <sid>so conversation survives. Other threads waiting onwith session:block on the lock until the new pid is ready.restart()(issue-boundary fresh-conversation reset) now also holds the lock and clearssession_idbefore respawn — the opposite ofswitch_model's context preservation.Closes #491.
EOF
)