Agent host: restored Claude sessions lose their slash commands, skills and agents until the next message
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
When an agent-host Claude session's live runtime is no longer resident, `getChatCustomizations` returns an empty list, so the session loses every customization-derived surface: project slash commands, plugin and built-in skills, custom agents, rules and hooks. A session created in the same window has all of them.
## Steps to reproduce
1. Open a Claude agent-host session in a workspace that has `.claude/commands/*.md` and send at least one message.
2. Put the session's runtime out of residency: close the window and reopen it, restart the host, or leave the session unsubscribed until idle eviction releases it.
3. Select the restored session and type `/` in the chat input.
**Expected:** the completions a session created in this window offers, namely the project's commands plus the built-in skills.
**Actual:** only host-level commands appear (`/rename`). Sending any message restores the full list.
Observed on a live host, two sessions in the same workspace seconds apart, with the identical request `{"kind":"userMessage","text":"/","offset":1}`:
| session | state | completion items |
|---|---|---|
| created in this window | live runtime | 18 (5 project commands + 13 built-in skills) |
| restored into this window | backing only | 1 (`/rename`) |
The restored session is missing the read-only built-ins (`agent-builtin:/skill/*`) as well as the project commands, which is what distinguishes this from a directory scan that failed to read the workspace.
## Root cause
`ClaudeAgent.getChatCustomizations` resolves the chat through its **live** runtime:
```ts
const sess = this._findChatByUri(chat);
if (!sess) {
return [];
}
```
`_findChatByUri` maps the chat to its backing's `sdkSessionId` and looks that up in `_chatEntriesBySdkId`, which holds only live `ClaudeAgentSession` wrappers. Two ordinary states have a backing but no wrapper:
- `_releaseChat` disposes the live session on idle eviction and deliberately keeps the backing: "`_chatBackings` retains the backing across release so the chat resolves uniformly on the next cold resume-on-send".
- `materializeChat` re-attaches the backing for a session restored into a fresh process, and registers no wrapper by design.
In both, the read returns `[]`.
Nothing downstream recovers from that:
- `AgentService._doRestoreSession` calls `getChatCustomizations` on exactly this path and seeds state only `if (restoredCustomizations && restoredCustomizations.length > 0)`, so `SessionState.customizations` stays `undefined`.
- `AgentHostSkillCompletionProvider._getCandidates` resolves `/` completions through the same call, so the completion list carries no commands or skills.
- `AgentSideEffects._publishSessionCustomizations` returns early when the resolved set matches session state, so no `SessionCustomizationsChanged` is dispatched either. Confirmed on the live host: the restored session received zero of those actions over its lifetime, while the sessions created in the window received theirs.
The empty list is not the intended pre-materialize behaviour. `ClaudeAgentSession.getSessionCustomizations` supports a session with no SDK Query explicitly, and documents it: "Pre-materialize there is no Query, so the full disk set is shown". A restored chat never reaches that code.
The sibling providers do not share the problem. `CopilotAgent.getChatCustomizations` resolves from `resolveAgentChatContext(context, chat).configurationResource` and handles the "Provisional (pre-send) or pre-resume" case explicitly; `CodexAgent.getChatCustomizations` resolves through `_resolveConversationSession`. `ClaudeAgent` is the only provider keyed on a live runtime, and the only one that ignores its `context` argument.
## Suggested fix
Bring the chat's runtime up from its exact backing when it is not resident, reusing `_createProvisionalChatSession`, which recovers the working directories from the SDK transcript `cwd` or the persisted overlay and starts no SDK Query. That reaches the pre-materialize path `getSessionCustomizations` already implements, and leaves the live-runtime case untouched. I have this working locally with a regression test and am happy to open a PR.
## Version
Visual Studio Code 1.1.3 (stable), server commit `110a328ea54b42367b803ec53ee0bf52ef26b419`, dev container on linux-arm64. Confirmed against `main` in the source refs quoted above.
*AI disclosure: this issue and the related code were written with the assistance of AI.*
### Public patches and patcher scripts
[Public patch catalog and patcher scripts](https://github.com/RyanEwen/vscode-patches/blob/main/CATALOG.md) · [Source patch index](https://github.com/RyanEwen/vscode-patches/blob/main/SOURCE-PATCHES.md). The [public collection](https://github.com/RyanEwen/vscode-patches) includes the maintained patchers, rollback instructions, regression scripts, and historical snapshots. Build restrictions and exact installer coverage are documented there.
Contributor guide
Assessment
This issue has not been assessed yet.