Files
claude-mode/docs/live-sessions.md
smoidoandClaude Opus 5 b514e00745 README down to what a user needs; the rest into docs/
The README had grown to 1,010 lines of user docs and design notes in one file,
and had gone stale: it still showed the old numbered menu, set up only three
providers, and listed preflight checks and a file layout that predate the last
three releases. It now carries install, first run, the full command
reference, the providers at a glance, troubleshooting and a docs index.

docs/:
- providers.md    presets, defaults, the model cache, context windows, each
                  provider (OpenRouter's cost guard and guardrail check are
                  written up for the first time), adding a provider
- live-sessions.md  what a switch does to running sessions, and repair
- design.md       why settings.json, why keys stay out of it (and the vault
                  per platform), the preflight checks as they are now
- bar-widget.md   the widget as it is now: providers from health.json, every
                  server provider's settings, the restart after an upgrade
- architecture.md the pieces, every file on disk and who writes it, the
                  contracts between them, where to change what
- development.md  running the tests, the conventions the code follows,
                  working on the widget, releasing
CONTRIBUTING.md points at it.

Also:
- The per-project session listing used awk, which the CLI avoids because it
  is missing from minimal images; it uses the script's own TSV helpers now,
  and tests/static.sh fails on any awk in the CLI.
- tests/static.sh checks every relative Markdown link and #anchor.
- A unit test pins the managed env keys between cm-json.py and
  claude-mode.ps1, which only a comment kept in step before.
- test_sessions covers the per-project listing, which nothing ran.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 01:52:03 +03:00

160 lines
7.8 KiB
Markdown

# Live sessions
What a switch does to the Claude Code sessions already running, and how
claude-mode keeps that from costing you work. The incident that led to all of
this is written up in [incident-mode-switch-corrupts-live-sessions.md](incident-mode-switch-corrupts-live-sessions.md).
## Why a switch breaks a running session
The two halves of the config behave differently.
The static half — base URL, model ids, the env block — really is 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`), and the helper
answers for whatever `state.json` says *at that moment*. So a switch reaches into
a live session through the one thing that was never cached:
| switching to | what the running session gets |
|---|---|
| `anthropic` | nothing — the helper returns no credential in that mode, by design |
| another provider | the new provider's key, sent to the old base URL, which rejects it |
| another preset of the *same* provider, same key | same key, same endpoint — this one survives, on the model ids it started with |
Either of the first two starts failing calls whenever the timer next fires —
mid-turn as easily as between turns. So restart the affected sessions, or let
`claude-mode sessions --restart` do it:
- **CLI** — exit and relaunch `claude`
- **VS Code** — `Ctrl+Shift+P`*Developer: Reload Window*
- **Desktop app** — quit and reopen
`claude-mode status` shows what the *next* launch will use.
## The part that is not just an inconvenience
A failed call is recoverable. A *successful* one may not be.
If a running session takes even one completion from the new provider before
anything notices, that provider's message-id format lands in its transcript.
OpenRouter issues `gen-<epoch>-<rand>` where Anthropic issues `msg_…`, and native
Anthropic then refuses to resume the session at all:
```
API Error: 400 diagnostics.previous_message_id: must be the `id` from a
prior /v1/messages response (starts with `msg_`)
```
There is no supported way back from that. The transcript has to be rolled back to
the last message Anthropic issued:
```bash
claude-mode repair-session # transcripts for this project
claude-mode repair-session --all # every project, problems only
claude-mode repair-session <session-id> # show what it would cut
claude-mode repair-session <session-id> --apply
```
A bare listing covers only the project you are standing in (walking up from the
current directory to find it), while a **named session id is looked up across
every project** — you rarely remember which project a session you cannot resume
belonged to. `--all` drops the scoping entirely.
`--all` reports only what is actually actionable. Of 59 transcripts here it once
flagged 16; 5 had simply never received a reply, and 10 had run start to finish
on a gateway, so every id in them is that provider's by design — they resume fine
under the provider they were born on. Neither is damage, so neither is listed.
Only a transcript with a genuine `msg_` message *and* another provider's output
after it is something this can or should touch.
### Dismissing a session
Not every broken session is worth repairing — a throwaway, or one whose work was
finished some other way — and one that can never be cleared keeps the bar's
warning dot lit for good. So a session can be dismissed, and one untouched for
more than 7 days is hidden on its own:
```bash
claude-mode repair-session --ignore <session-id> # stop counting it
claude-mode repair-session --ignored # what is dismissed, and whether it is still broken
claude-mode repair-session --unignore <session-id> # or --unignore-all
claude-mode repair-session --all --max-age 0 # include the age-hidden ones
```
`CM_IGNORE_AGE_DAYS` changes the default age. Neither touches the transcript.
Hidden sessions are always counted — `--all` ends with a line like
`2 hidden: 1 ignored, 1 older than 7 days` — so age-hiding never looks like damage
disappearing. Dismissals live in `~/.claude-mode/ignored-sessions.json`; an entry
is dropped when its transcript is deleted and when the session is repaired, so a
session that breaks again later is not silently hidden.
## The cut turns are not thrown away
Truncating is the mechanical fix, but the turns being cut are the work itself —
and a session that resumes with a hole in its memory is barely resumed at all. So
`--apply` does three things before it deletes anything:
1. **Backs up** the original as `<session>.jsonl.pre-repair-backup-<stamp>`.
2. **Writes the dropped turns out** as `<session>.recovered-<stamp>.md` — what was
asked, what was answered, what was run. Tool *results* are left out; they are
most of a transcript by volume and the least useful part of a summary.
3. **Hands them back to the session** as one appended note, so the agent that
resumes knows what it just did.
That note is a `user` entry marked `isMeta` — the marker Claude Code uses for its
own local-command caveats, meaning "context, not something to answer". It carries
**no `message.id`**, so it cannot re-create the very condition being repaired.
`--no-reinject` writes the Markdown but leaves the session untouched.
It refuses to touch a transcript written to in the last 90 seconds, since that
belongs to a session still alive.
## The switch asks first
While any sessions are running, a switch stops and asks: restart them (the only
answer that ends with everything on the new mode), close them, proceed anyway, or
abort — and abort is the default. Non-interactively it refuses outright unless
given `--yes`.
```
claude-mode sessions
running sessions (2)
562250 pts/4 /home/you/Work working (this session - never touched)
631644 pts/1 /home/you/Projects/api
```
Sessions are found through `/proc/<pid>/exe`, which resolves to the real `claude`
binary — a process-name match would sweep up every shell that merely mentions
claude on its command line. Two things are then filtered out:
- **The calling session.** It is listed, and never signalled.
- **Forks of a session.** A busy session spawns children off its own binary, and
they inherit the same `exe`; without excluding anything whose parent is itself
claude, the count climbed and fell with load (2, 5, 11 and 40 on the same two
sessions). A real session's parent is a terminal.
`working` is a sampled-CPU heuristic — two reads of `utime + stime` 300ms apart —
so it is a good guess about which session is mid-turn, not a promise.
`--stop` sends SIGTERM, never SIGKILL: Claude Code writes out its transcript on
the way down. `--restart` stops each session and reopens it, re-running the parent
terminal's own command line where there is one, so the same terminal, flags and
directory come back. Interactive runs confirm first; `--yes` is for callers that
have already asked; `--dry-run` prints the plan and touches nothing. All of this
needs `/proc`, so it is Linux-only.
## The bar notices for you
Nothing tells you a session is unresumable until you try to resume it, by which
point you have usually forgotten which one it was. So the bar widget scans every
project on a timer (and whenever the panel opens) and puts a dot on its icon when
there is something to fix. The panel lists the affected sessions, with **Repair**
(after saying what it will drop and keep) and **Ignore** beside each, and the
hidden ones under a collapsed `hidden (N)` row with **Restore**.
The scan is affordable because it reads the *tail* of each transcript first: if
the last message is Anthropic's, the transcript is healthy and the rest of the
file is never opened. That is the overwhelmingly common case, so a sweep of 59
transcripts costs about 60ms, against about 5s for reading every byte.