Skip to content

agentHost: resolve Claude customizations for a chat that is not resident - #332055

Open
Ryan Ewen (RyanEwen) wants to merge 1 commit into
microsoft:mainfrom
RyanEwen:fix/cold-chat-customizations
Open

agentHost: resolve Claude customizations for a chat that is not resident#332055
Ryan Ewen (RyanEwen) wants to merge 1 commit into
microsoft:mainfrom
RyanEwen:fix/cold-chat-customizations

Conversation

@RyanEwen

@RyanEwen Ryan Ewen (RyanEwen) commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Fixes #332047

Problem

ClaudeAgent.getChatCustomizations resolves the chat through its live runtime:

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 and no wrapper:

  • _releaseChat disposes the live session on idle eviction and keeps the backing on purpose: "_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.

In both, the read returns [], so the session reports no project commands, no skills, no agents, no rules, not even the read-only built-ins, until its next message. AgentHostSkillCompletionProvider resolves / completions through this call, so the session's slash commands are gone; AgentService._doRestoreSession seeds state only when the resolved set is non-empty; and AgentSideEffects._publishSessionCustomizations skips its dispatch when the resolved set already matches state, so nothing republishes later either.

An empty list is not the intended pre-materialize behaviour. ClaudeAgentSession.getSessionCustomizations supports a session with no SDK Query and documents it: "Pre-materialize there is no Query, so the full disk set is shown". A non-resident chat simply never reaches it. CopilotAgent.getChatCustomizations resolves from resolveAgentChatContext(context, chat).configurationResource and handles the "Provisional (pre-send) or pre-resume" case explicitly, and CodexAgent.getChatCustomizations resolves through _resolveConversationSession. ClaudeAgent is the only provider keyed on a live runtime, and the only one that ignored its context argument.

Change

Read through a throwaway runtime built from the chat's exact backing. _createProvisionalChatSession is split into _buildProvisionalChatSession, which only builds, and a wrapper that registers it and re-applies client contributions for the send path. The read uses the build step, which starts no SDK Query and recovers the working directories from the SDK transcript cwd or the persisted overlay.

That runtime is never registered and is disposed when the read returns, so the read mutates no provider state. A resident one would make _ensureResolvedChatSession take the chat for a provisional one on the next send and materialize it fresh, and isResume: false starts the SDK with sessionId rather than resume, so a chat that has a transcript would silently begin a new conversation. The second test locks that. It would also queue an active-client refresh onto the session sequencer that could only run once the read had finished.

A failure reports no customizations rather than throwing, so a chat whose working directory cannot be recovered degrades to today's behaviour. The live-runtime path is unchanged.

Not covered: the client-pushed plugin tier, which a chat with no resident runtime still omits. Adopting it means running the plugin sync on every read, and reads happen per completion keystroke.

How to test

  1. Open a Claude agent-host session in a workspace that has .claude/commands/*.md, and send a message.
  2. Close the window and reopen it, so the session is restored into a fresh host process.
  3. Select the restored session and type / without sending anything.

The project's commands and the built-in skills are offered. On main, only /rename is.

Verified end to end in a dev container on 1.1.3: before the change the restored session's completions returned 1 item, after it they return 18 (5 project commands plus 13 built-in skills).

Unit coverage adds two tests to claudeAgent.test.ts: one that the read returns the pre-materialize set for a chat released from memory, and one that the read leaves no resident runtime behind and that the following send still resumes the existing conversation.

AI disclosure: this pull request and the related code were written with the assistance of AI.

Copilot AI balanced review requested due to automatic review settings August 21, 2026 22:27
@vs-code-engineering

Copy link
Copy Markdown
Contributor

📬 CODENOTIFY

The following users are being notified based on files changed in this PR:

TylerLeonhardt

Matched files:

  • src/vs/platform/agentHost/node/claude/claudeAgent.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Restores Claude customizations for non-resident chats using a temporary provisional runtime.

Changes:

  • Resolves and disposes a transient runtime under session sequencing.
  • Adds regression coverage for customization reads and subsequent resume behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
claudeAgent.ts Adds non-resident customization resolution.
claudeAgent.test.ts Tests restored customization reads and resume behavior.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/vs/platform/agentHost/node/claude/claudeAgent.ts Outdated
`getChatCustomizations` resolved the chat through its live runtime, so a chat with a
backing but no resident runtime reported no customizations at all: no project commands,
no skills, no agents, not even the read-only built-ins. That is every session restored
into a fresh host process and every session idle eviction released, and it leaves the
session without slash commands until its next message.

Read through a throwaway runtime built from the chat's exact backing instead.
`_createProvisionalChatSession` is split into `_buildProvisionalChatSession`, which only
builds, and a wrapper that registers it and re-applies client contributions for the send
path. The read uses the build step, which starts no SDK Query and recovers the working
directories from the SDK transcript cwd or the persisted overlay, so it reaches the
pre-materialize path `getSessionCustomizations` already implements.

The throwaway runtime is never registered and is disposed when the read returns. A
resident one would make the next send take the chat for a provisional one and materialize
it fresh rather than resume it, and it would queue an active-client refresh onto the
session sequencer that could only run after the read had finished.

Fixes microsoft#332047

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvement. Clean separation of concerns.

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.

Agent host: restored Claude sessions lose their slash commands, skills and agents until the next message

4 participants