vt-t-session-restore¶
This skill should be used after a re-login, a session-limit reset, or a switch to a second Anthropic account or to another org of the same login, when Claude Code sessions across several repositories need to be brought back to where they stopped. For desktop-app work it reports which sidebar sessions the new account or org is missing, puts chosen ones — or all of them — back into the sidebar with
claude-boot adopt, and hands over a ready opening message for the rest, without opening a terminal; for CLI work it resumes conversations one terminal window per repository. It surveys every conversation on disk and names which repositories carry uncommitted work before anything is started. Trigger phrases include "restore my sessions", "bring my sessions back", "I switched accounts", and "start everything again".
Plugin: vt-toolkit
Category: Other
Command: /vt-t-session-restore
Session Restore¶
Overview¶
Claude Code stores every conversation as a JSONL transcript under
~/.claude/projects/<encoded-cwd>/<session-id>.jsonl, keyed by folder. The desktop app's
session sidebar is stored separately under claude-code-sessions/<account>/<org>/, keyed by
account and org — one login can hold several orgs, and each org has its own sidebar. That
asymmetry is the whole point of this skill: after an account or org switch the sidebar looks empty
while every conversation is still on disk, untouched and resumable.
This skill restores those conversations and reports what it finds before it acts. Which route applies depends on where the work was: a conversation from the desktop app belongs back in the desktop app (Step 1), one from the CLI belongs in a terminal (Steps 2-4).
When to use¶
- After logging out and back in, or after switching to a second account because a session limit hit
- After switching to another org of the same login (team plan ↔ private plan) — the same situation
- After killing stale processes (an expired OAuth session leaves sessions alive but unable to work)
- When the sidebar is empty but yesterday's work should still exist
- Before a working day, to see which repositories carry uncommitted work
Workflow¶
Step 1 — The desktop report (start here after an account or org switch)¶
Compares the sidebars in the desktop store — (account, org) pairs — and prints, per session the current sidebar is missing: the folder, the git state and a ready opening message whose first instruction is the switch into that folder. It opens no window and starts no process.
Read the first lines: current account: names the account and the rule that decided it, and
current org: the org, when a rule could name one (this session's own entry, or a scratch path).
Without a current org: line only the account is known and the comparison is account-wide — an org
switch inside that account is then invisible; say so. If the account is wrong, everything below it
is inverted — say so rather than reading on.
An exit code of 1 means drift: a store was there but yielded nothing, so the layout may have changed. Report that as a finding; it is not "nothing is missing".
A section OFFENE RÜCKFRAGEN lists the current account's sessions that wait on something. Relay it
before the user quits the desktop app: quitting records every open question as a rejection by the
user, and the session then proceeds as if the user had declined (references/mechanics.md). A line
Rückfrage: is a question; a line Werkzeug: is an unanswered tool call — often a pending
approval, which the transcript alone cannot confirm. Say the second half too: answer the listed
questions and then quit right away — a question that appears in between is rejected the same
way, and nothing afterwards can tell it apart from an answer the user gave.
How to read the rest of the report:
| Section | Means | Exit code |
|---|---|---|
FEHLT: … |
a session of another sidebar with no counterpart here — put it back with adopt, or restart it with the message below it |
0 |
Auch in: … under a FEHLT |
the same conversation sits in further sidebars; the block shows the newest live copy, which adopt takes |
0 |
Hinweis: … --allow-shared-folder |
the folder already has a session here; adopt needs that flag for a second entry on it |
0 |
Hinweis: … --replace-older |
this sidebar holds the same entry with another, older conversation; adopt overwrites it only with that flag |
0 |
DOPPELT im aktuellen Konto |
two entries of this account on one folder or one conversation; nothing is missing, the sidebar is just cluttered | 0 |
no second account found |
only one sidebar in the store (one account, or with a known org one account/org pair), so only duplicates can be reported | 0 |
HINWEIS: N Eintrag(e) nicht lesbar |
the report is incomplete by that many entries — everything else still holds | 0 |
BEFUND: … nicht gefunden |
a store was not there at all | 0 |
BEFUND: … vorhanden, aber … + Keine Aussage über fehlende Sessions möglich. |
drift: the store exists and yielded nothing, so no count is trustworthy | 1 |
"0 Sessions fehlen" is a statement about what the rules compared, not proof that nothing is
gone: archived entries and entries whose conversation a live entry of the current sidebar already
has are dropped silently (references/mechanics.md). An entry whose folder the sidebar already claims is reported
since BUG-309, with a Hinweis.
Step 1a — Put a missing session back (adopt)¶
A missing session's entry still exists, in the previous sidebar's folder. adopt copies it into
the current one, which is all the sidebar needs: the entry reappears, opens its history and
continues the original transcript (measured 2026-09-16, references/mechanics.md). Creating a
session that never had an entry is still the user's part — for that there is no interface.
The order is not free, and each step exists because of what the step before it costs:
- Pick the ids with the user, in the chat. The report prints one per missing session. This
skill never picks for them. After a switch the usual answer is "all of them": then the choice
left is the two opt-ins, and each is the user's —
--allow-shared-folder(a second entry on a folder that already has one) and--replace-older(overwrite this sidebar's entry of the same name when the other copy is newer). Ask about both; do not add them unasked. - Run it inside the app anyway:
It refuses —
bash ${CLAUDE_PLUGIN_ROOT}/skills/session-restore/scripts/claude-boot adopt <id>... bash ${CLAUDE_PLUGIN_ROOT}/skills/session-restore/scripts/claude-boot adopt --all [--allow-shared-folder] [--replace-older]adoptwrites only with the app closed — and that refusal is the point: it prints the open questions that quitting would turn into rejections, and the full command for afterwards, with--account,--org, the opt-ins and the ids expanded —--allincluded, so the run afterwards copies exactly the list the user saw. Relay both verbatim, together with theabgelehnt:lines above them: each names the session and the reason it stays out.
A conversation that sits in several other sidebars is taken from the copy with the newest
activity; the refusal says which copy that is (Hinweis: … neueste von N Kopien). A live copy
beats an archived one whatever their age, so adopt takes the copy the report showed
(Hinweis: … archiviert, nicht genommen). Copies with the same file name and the same
bytes count as one (Hinweis: … byte-gleich). Only live copies with equal activity that still
differ stay mehrdeutig; the line under the refusal names the fields they differ in
(unterscheiden sich in: …), so the user can decide which copy to take by hand. An entry the
target sidebar holds archived does not make a session "already here": it is brought back,
and an archived entry of the same file name is replaced without --replace-older
(Hinweis: … ersetzt den archivierten Eintrag hier; the undo restores it).
--account/--org name the target sidebar — the account and org written into, normally
the current ones. They are never where the sessions come from; that is already on each id.
Passing the source account and org makes adopt refuse every id with "schon im aktuellen
Konto", which reads like an entirely different failure. Passing the source account with a
different org that account holds is worse: nothing refuses, and the copies land in that
sibling org. (An org the account does not have is refused with "Org-Ordner fehlt".) This is why step 4 says to run the printed command unchanged.
3. Answer the listed questions, then quit right away. A question a session asks between the
answer and the quit is rejected by the quit as well, and nothing afterwards can tell that
rejection from an answer the user gave (EC-16, mechanics.md).
4. Run the printed command in Terminal.app, unchanged. Never reconstruct the flags: they are
the --account/--org passed in step 2, or — when none were passed — this session's own
entry, and a guessed account writes into someone else's sidebar. A sidebar is an account
and an org: one login can hold several orgs, and each has its own sidebar.
5. Keep the undo command the run prints. It is the only line that names the backup directory.
6. Start the app. The adopted sessions are in the sidebar.
If the printed command still says the app is running although it is quit, something else inside
Claude.app/Contents/ is alive. The browser extension's chrome-native-host, which Chrome or
Comet keep running, no longer counts (BUG-310); any other process there does, on purpose.
To reverse a run:
bash ${CLAUDE_PLUGIN_ROOT}/skills/session-restore/scripts/claude-boot adopt --undo <dir> --account <a> --org <o>
--account/--org name the same target sidebar the run itself was given — the account and
org the entries were written into and are now removed from.
The undo removes only what that run created, and puts back from the backup what it replaced
(--replace-older) — checked against the store, the flags and the backup, never against its own
record. Restoring the whole backup directory by hand is a different act: it
also rolls back everything the app itself wrote since, which is the user's decision to make.
For the sessions that are not adopted, hand over the folder and the opening message, one at a time.
Step 2 — Build the inventory (CLI conversations)¶
bash ${CLAUDE_PLUGIN_ROOT}/skills/session-restore/scripts/claude-boot scan
bash ${CLAUDE_PLUGIN_ROOT}/skills/session-restore/scripts/claude-boot inventory
scan writes one entry per repository (the most recent conversation) to
~/.claude/session-boot.json. inventory records every conversation, with its topic, its
closing state and its branch, to ~/.claude/session-inventory.json.
scan takes cli conversations only and names what it sets aside — a desktop conversation
resumed in a terminal is the detour Step 1 exists to avoid; CLAUDE_BOOT_ALL_ENTRYPOINTS=1 includes
every surface. inventory records every conversation of the manifest's repositories, whatever its
surface, and marks each with its entrypoint. A manifest written before schema v2 is refused with a named reason rather than read: its
dates come from file mtimes.
Step 3 — Report before acting¶
Present this to the user, and call out explicitly:
- which repositories already show
●(a session is running —openwill skip them) - which carry uncommitted files, since a resumed session may commit or push on its own
- which repositories hold several recent conversations, because
openrestores only one per folder and may not pick the intended one
For the third point, show the alternatives rather than guessing:
Step 4 — Restore¶
One terminal window per repository, running claude --continue --remote-control <slug>. Repositories
with a running session are skipped; CLAUDE_BOOT_FORCE=1 overrides that.
To restore a specific conversation instead of the newest one:
Step 5 — Handle a blocked terminal¶
open and resume drive Terminal.app through AppleScript. In a session without automation
permission this fails. Do not retry it — fall back to printing the commands for the user to paste:
Step 6 — Suppress the trust prompt (optional, only on request)¶
A first CLI start in a folder asks whether the folder is trusted, and that prompt blocks the session before it registers with Remote Control — so it is invisible from another device.
bash ${CLAUDE_PLUGIN_ROOT}/skills/session-restore/scripts/claude-boot-trust
bash ${CLAUDE_PLUGIN_ROOT}/skills/session-restore/scripts/claude-boot-trust --apply
Without --apply it only reports. Never run --apply unprompted: it disables a security question
for every repository in the manifest. Ask first, and say what it does.
Step 7 — Write a handoff (optional, for a session that will not be resumed as-is)¶
The report says which session is missing and where. When the work is complex enough that the next session should not have to reconstruct it — mid-review, uncommitted fix sets, decisions already taken — write a handoff instead of relying on the opening message alone.
- Input: the conversations of that folder (all entrypoints), plus its git state.
- Output:
~/.claude/handoffs/<project>-<YYYY-MM-DD>.md— outside any repository, because a handoff quotes conversation text verbatim and that must not land in a commit. - Shape:
specs/203-session-restore-desktop-report/examples/handoff.mdin the toolkit repo. - Optional by design: for a session that simply continues, the opening message is enough.
Every claim in a handoff carries its evidence or is marked unchecked, and owner decisions are recorded as decisions — the next session will act on what it reads.
What to warn about¶
- Resumed sessions act immediately. They continue mid-task, up to and including a push. After
open, advise a look at the windows before anything else. --continuetakes the newest conversation in a folder, which is not always the wanted one.- File modification times lie. A process writes on shutdown, so transcripts appear freshly
touched. Every date this skill prints therefore comes from the last real message;
listand the manifest carry it explicitly, and a pre-v2 manifest is refused rather than misread. - A folder that is not a repository reports "kein Repo", never the enclosing checkout's branch. Git walks up the tree, so the naive question answers for the parent — the report does not ask it.
- Occupancy is resolved by working directory, not by the session slug. When it cannot be
resolved (no
lsof, unreadable process list) the folder counts as occupied, andopenskips it. A needless skip costs a line; a second process on one transcript costs the conversation. - An adopted session sits in two sidebars.
adoptcopies, it never moves: the entry stays in the other account or org too. Opening the same session in both at once puts two processes on one transcript — work in one of them. - A start that produced no process is reported as
FEHLSTART, separately from the started ones.osascriptreturns before a process exists, so a silent no-start used to count as success.
Reference¶
references/mechanics.md — why folder-keyed transcripts survive an account switch, what the
manifest and inventory contain, and how the pieces fit together.
Next actions¶
- Run
claude-boot desktopfirst and show the user its first line: the current account and the rule that chose it. If that account is wrong, stop — everything below it is inverted. - Relay the OFFENE RÜCKFRAGEN section, if any, before the user quits or switches anything: quitting the app records each open question as a rejection.
- Agree which missing sessions to put back — or all, with the user's choice on
--allow-shared-folderand--replace-older— then runclaude-boot adopt <ids>orclaude-boot adopt --all …inside the app and relay its refusal in full: the open questions it lists are lost to the quit, and the command it prints is the one to run afterwards, unchanged. - After the user's Terminal run, keep the printed undo command in the conversation — it names the backup directory, and nothing else does.
- For each session that is not adopted, hand over its folder and its opening message, one at a time; the user creates the desktop session and pastes the message.
- Only for conversations that really are CLI work: show
claude-boot list, let the user pick withclaude-boot sessions <repo>where a repository has several, thenclaude-boot openorclaude-boot resume <id>— or hand overclaude-boot printwhen terminal automation is blocked.