Correct what a switch actually does to a running session
Every one of these said a switch leaves running sessions alone - that they keep talking to the old provider until restarted. That is wrong, and reassuring in exactly the wrong direction: a switch breaks them. The static half of the config does behave as described. Base URL, model ids and the env block are read once at startup and a running session keeps what it started with. The credential is not. It comes from running apiKeyHelper, which Claude Code re-invokes on a timer - CLAUDE_CODE_API_KEY_HELPER_TTL_MS is present in the 2.1.251 binary - and claude-key-helper.sh answers for whatever state.json says at that moment. So the switch reaches into a live session through the one part that was never cached. Into anthropic, the helper returns nothing by design and the next refresh comes back with no credential; into another provider, it hands over the new key while the session is still pointed at the old base URL, which rejects it. Either way the session starts failing calls whenever the TTL happens to expire, mid-turn as easily as between turns. One case does survive, and is now stated rather than glossed: switching between two presets of the same provider that share a keyRef keeps the same key and the same endpoint, so the session carries on with the model ids it started with. The claim in "Restarting sessions" predates this work - it came in with the Windows build - but it is wrong for the same reason and is corrected too.
This commit is contained in:
+29
-10
@@ -539,14 +539,31 @@ cmd_preflight() {
|
||||
# ---------------------------------------------------------------------------
|
||||
# Running sessions
|
||||
#
|
||||
# Claude Code reads settings.json once, at startup. A switch therefore does
|
||||
# nothing to a session already running - it keeps talking to the old provider on
|
||||
# the old key until it is restarted, which is the confusing part: the bar says
|
||||
# one thing and the session in front of you is doing another.
|
||||
# A switch breaks running sessions. Not "leaves them on the old provider" -
|
||||
# breaks them, and it is worth being exact about why, because the two halves of
|
||||
# the config behave differently.
|
||||
#
|
||||
# Worse for a session mid-request. The key it is using can be pulled out from
|
||||
# under it (anthropic mode deletes the helper outright), so an in-flight turn
|
||||
# can fail on the next tool call rather than at a clean boundary.
|
||||
# The static half - base URL, model ids, the env block - really is read once at
|
||||
# startup, and a running session keeps the values it started with.
|
||||
#
|
||||
# The credential is not. It is fetched by running apiKeyHelper, which Claude
|
||||
# Code re-invokes on a timer (CLAUDE_CODE_API_KEY_HELPER_TTL_MS, present in
|
||||
# 2.1.251), and the helper answers for whatever state.json says *now*. So a
|
||||
# switch reaches into a live session through the one thing that was never
|
||||
# cached:
|
||||
#
|
||||
# -> anthropic the helper returns nothing at all, by design, and the
|
||||
# session's next refresh comes back with no credential
|
||||
# -> another provider
|
||||
# the helper hands over the new provider's key while the
|
||||
# session is still pointed at the old base URL, which rejects
|
||||
# it
|
||||
#
|
||||
# Either way the session starts failing its calls, at whatever moment the TTL
|
||||
# happens to expire - mid-turn as easily as between turns. The one case that
|
||||
# does survive is a switch between two presets of the same provider sharing a
|
||||
# keyRef: same key, same endpoint, and the session simply carries on with the
|
||||
# model ids it started with.
|
||||
#
|
||||
# Sessions are found through /proc/<pid>/exe rather than by matching process
|
||||
# names. `claude` is a real ELF binary here, so the symlink resolves to it
|
||||
@@ -690,7 +707,7 @@ $rows
|
||||
EOF_ROWS
|
||||
|
||||
if [ "$action" = "list" ]; then
|
||||
printf '\n %sthese keep the provider they started with until restarted%s\n' "$C_DIM" "$C_RESET"
|
||||
printf '\n %stheir credential is re-fetched on a timer, so a switch breaks them%s\n' "$C_DIM" "$C_RESET"
|
||||
printf ' %sclaude-mode sessions --stop close them%s\n' "$C_DIM" "$C_RESET"
|
||||
printf ' %sclaude-mode sessions --restart close and reopen each in its own directory%s\n' "$C_DIM" "$C_RESET"
|
||||
printf ' %s--dry-run shows what either would do, and does nothing%s\n' "$C_DIM" "$C_RESET"
|
||||
@@ -803,9 +820,11 @@ cm_report_live_sessions() {
|
||||
|
||||
busy="$(printf '%s' "$rows" | cut -f4 | grep -c '^yes$' || true)"
|
||||
printf '\n'
|
||||
warn "$n Claude Code session(s) still running on the previous provider"
|
||||
warn "$n Claude Code session(s) are running and will start failing their calls"
|
||||
say 'their key is re-fetched on a timer and now resolves to the new mode, which'
|
||||
say 'the endpoint they are still pointed at will not accept. Restart them.'
|
||||
if [ "${busy:-0}" -gt 0 ]; then
|
||||
warn "$busy of them is mid-request - it may fail on its next call rather than at a clean stop"
|
||||
warn "$busy of them is mid-request and will break wherever it happens to be"
|
||||
fi
|
||||
printf ' %sclaude-mode sessions what is running%s\n' "$C_DIM" "$C_RESET"
|
||||
printf ' %sclaude-mode sessions --restart close and reopen them on the new provider%s\n' "$C_DIM" "$C_RESET"
|
||||
|
||||
Reference in New Issue
Block a user