Skip to content

ClaudeSession: --model flag + restart-based switch with --resume (closes #491)#492

Merged
FidoCanCode merged 1 commit into
mainfrom
hotfix-switch-model-hang
Apr 14, 2026
Merged

ClaudeSession: --model flag + restart-based switch with --resume (closes #491)#492
FidoCanCode merged 1 commit into
mainfrom
hotfix-switch-model-hang

Conversation

@FidoCanCode
Copy link
Copy Markdown
Owner

Stream-json doesn't accept the /model slash command. ClaudeSession.switch_model was sending /model <name> over stdin then draining iter_events — claude echoed "Unknown command: /model" and never emitted a turn boundary. First switch_model call hung forever. Compounded: set_status holds the global status_update lock for its whole flow, so once one worker's set_status wedged on emoji generation (via the session), every other worker's set_status blocked. Kennel deadlocked.

Fix

Research (see #491 thread): no stream-json control_request for model switching exists; --model at spawn time + --resume <session_id> across swaps is the only path.

  • ClaudeSession gains a model kwarg (default claude-opus-4-6), threaded into _spawn as --model.
  • iter_events now captures the latest session_id from stream-json events.
  • switch_model: no-op when model matches current; otherwise holds _lock for the full swap, kills the old proc, respawns with --model <new> --resume <sid> so conversation survives. Other threads waiting on with session: block on the lock until the new pid is ready.
  • restart() (issue-boundary fresh-conversation reset) now also holds the lock and clears session_id before respawn — the opposite of switch_model's context preservation.

Closes #491.
EOF
)

…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.
@FidoCanCode FidoCanCode requested a review from rhencke April 14, 2026 19:58
@FidoCanCode FidoCanCode merged commit 948d829 into main Apr 14, 2026
2 checks passed
@FidoCanCode FidoCanCode deleted the hotfix-switch-model-hang branch April 14, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClaudeSession.switch_model (/model slash cmd) hangs: stream-json mode returns 'Unknown command: /model'

2 participants