Skip to content

feat: implement ClaudeCodeFolderMruService for managing recently used folders#310349

Merged
TylerLeonhardt merged 1 commit intomainfrom
tyler/reasonable-egret
Apr 16, 2026
Merged

feat: implement ClaudeCodeFolderMruService for managing recently used folders#310349
TylerLeonhardt merged 1 commit intomainfrom
tyler/reasonable-egret

Conversation

@TylerLeonhardt
Copy link
Copy Markdown
Member

This will help us adopt SessionOptionGroupBuilder

… folders

This will help us adopt SessionOptionGroupBuilder
Copilot AI review requested due to automatic review settings April 15, 2026 23:55
@TylerLeonhardt TylerLeonhardt enabled auto-merge (squash) April 15, 2026 23:55
@TylerLeonhardt TylerLeonhardt self-assigned this Apr 15, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Screenshot Changes

Base: 863765d4 Current: d285478c

Changed (1)

chat/aiCustomizations/aiCustomizationManagementEditor/McpBrowseMode/Light
Before After
before after

blocks-ci screenshots changed

Replace the contents of test/componentFixtures/blocks-ci-screenshots.md with:

Updated blocks-ci-screenshots.md
<!-- auto-generated by CI — do not edit manually -->

#### editor/codeEditor/CodeEditor/Dark
![screenshot](https://hediet-screenshots.azurewebsites.net/images/cb32a3e854b5734fe5aaca2318f2e0a42ee821b05ea97883ea42c5ba95edb3c3)

#### editor/codeEditor/CodeEditor/Light
![screenshot](https://hediet-screenshots.azurewebsites.net/images/42624fbba5e0db7f32c224b5eb9c5dd3b08245697ae2e7d2a88be0d7c287129b)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements a Claude-specific folder MRU service and wires Claude chat session UX to use the new IChatFolderMruService API (instead of the deprecated IFolderRepositoryManager.getFolderMRU()), supporting upcoming adoption of SessionOptionGroupBuilder.

Changes:

  • Added ClaudeCodeFolderMruService to compute/merge “recent folders” from Claude sessions, recent Git repos, and current workspace folders (with caching + delete support).
  • Updated ClaudeChatSessionContentProvider and its unit tests to consume IChatFolderMruService.getRecentlyUsedFolders(...).
  • Registered IChatFolderMruService for Claude sessions in the chat sessions contribution and added dedicated unit tests for the new MRU service.
Show a summary per file
File Description
extensions/copilot/src/extension/chatSessions/vscode-node/claudeChatSessionContentProvider.ts Swaps MRU source to IChatFolderMruService for empty-workspace defaults/options.
extensions/copilot/src/extension/chatSessions/vscode-node/test/claudeChatSessionContentProvider.spec.ts Updates mocks/wiring to provide IChatFolderMruService in tests.
extensions/copilot/src/extension/chatSessions/vscode-node/chatSessions.ts Registers ClaudeCodeFolderMruService as the Claude implementation of IChatFolderMruService.
extensions/copilot/src/extension/chatSessions/claude/node/claudeCodeFolderMru.ts New MRU service implementation for Claude sessions (merge/sort/cache/delete).
extensions/copilot/src/extension/chatSessions/claude/node/test/claudeCodeFolderMru.spec.ts New unit tests covering extraction, filtering, merge behavior, sorting, caching, and deletion.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 2

Comment on lines +17 to +57
const WORKTREE_PATH_PATTERNS = ['.claude/worktrees/', '.worktrees/copilot-'] as const;

function isWorktreePath(path: string): boolean {
return WORKTREE_PATH_PATTERNS.some(pattern => path.includes(pattern));
}

export class ClaudeCodeFolderMruService implements IChatFolderMruService {
declare _serviceBrand: undefined;
private readonly removedFolders = new ResourceSet();
private cachedEntries: FolderRepositoryMRUEntry[] | undefined = undefined;

constructor(
@IClaudeCodeSessionService private readonly sessionService: IClaudeCodeSessionService,
@IGitService private readonly gitService: IGitService,
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
) { }

async getRecentlyUsedFolders(token: CancellationToken): Promise<FolderRepositoryMRUEntry[]> {
const cachedEntries = this.cachedEntries;
const entries = this.getRecentlyUsedFoldersImpl(token).then(entries => {
this.cachedEntries = entries;
return entries;
});

return (cachedEntries ? cachedEntries : await entries).filter(e => !this.removedFolders.has(e.folder));
}

private async getRecentlyUsedFoldersImpl(token: CancellationToken): Promise<FolderRepositoryMRUEntry[]> {
const mruEntries = new ResourceMap<Mutable<FolderRepositoryMRUEntry>>();

// We're getting MRU, don't delay session retrieve by more than 5s
const sessions = await raceTimeout(this.sessionService.getAllSessions(token), 5_000);

for (const session of (sessions ?? [])) {
if (!session.cwd) {
continue;
}
if (isWorktreePath(session.cwd)) {
continue;
}
const folderUri = URI.file(session.cwd);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

Worktree path filtering for sessions is based on isWorktreePath(session.cwd) where cwd is a raw filesystem path string. On Windows this will typically contain backslashes, so checks for patterns like .claude/worktrees/ and .worktrees/copilot- (with /) won’t match and worktree folders may incorrectly appear in the MRU. Consider normalizing the path before matching (e.g., build a URI and check its .path, or replace \\ with / / use a platform-agnostic regex) and add a regression test for a Windows-style path.

Copilot uses AI. Check for mistakes.
return entries;
});

return (cachedEntries ? cachedEntries : await entries).filter(e => !this.removedFolders.has(e.folder));
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

When cachedEntries is set, getRecentlyUsedFolders() returns the cache without awaiting the refresh promise (entries). If getRecentlyUsedFoldersImpl() rejects (e.g. session service throws), that rejection can become unhandled because nothing awaits/catches it. Consider explicitly handling errors for the background refresh (e.g. void entries.catch(...) and keep the last good cache).

Suggested change
return (cachedEntries ? cachedEntries : await entries).filter(e => !this.removedFolders.has(e.folder));
if (cachedEntries) {
void entries.catch(() => undefined);
return cachedEntries.filter(e => !this.removedFolders.has(e.folder));
}
return (await entries).filter(e => !this.removedFolders.has(e.folder));

Copilot uses AI. Check for mistakes.
@TylerLeonhardt TylerLeonhardt merged commit 070960a into main Apr 16, 2026
29 of 30 checks passed
@TylerLeonhardt TylerLeonhardt deleted the tyler/reasonable-egret branch April 16, 2026 00:23
@vs-code-engineering vs-code-engineering Bot added this to the 1.117.0 milestone Apr 16, 2026
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.

3 participants