Add archived state management and worktree sharing for chat sessions#316561
Merged
Conversation
- Introduced `archived` property in `ChatSessionMetadataFile` to track archived sessions. - Updated `IChatSessionMetadataStore` interface to include methods for setting and getting archived state. - Implemented logic in `ChatSessionMetadataStore` and `MockChatSessionMetadataStore` to handle archived sessions. - Added `getBlockingSiblingSessionsForFolder` utility to identify sessions that share worktrees. - Modified CLI chat session commands to respect archived state during session deletion and worktree cleanup. - Updated tests to cover new functionality and ensure proper behavior of session management.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a persisted "archived" flag to chat session metadata and introduces a worktree-sharing check so that deleting or archiving a CLI chat session does not destroy a worktree that other live (non-archived, non-sub-session) sessions still depend on. Also reshapes getSessionParentId to return { parentSessionId, kind } so that forked vs sub-session lineage can drive UI/parenting decisions.
Changes:
- New
archivedfield onChatSessionMetadataFile, plussetSessionArchived/getSessionArchivedon the metadata store and its mock. - New
getBlockingSiblingSessionsForFolderutility andIChatSessionWorkspaceFolderService.getAssociatedSessions, used to gate worktree cleanup on archive and on single/bulk delete in both V1 and V2 CLI command paths. getSessionParentIdnow returns{ parentSessionId, kind: 'forked' | 'sub-session' }, and only'sub-session'is surfaced as the UI parent; archive‑state changes persistarchivedand are wired through the V1 (chatSessions.ts) and V2 (copilotCLIChatSessions.ts) registrations.
Show a summary per file
| File | Description |
|---|---|
| extensions/copilot/src/extension/chatSessions/common/chatSessionMetadataStore.ts | Adds archived field, refines getSessionParentId return type, adds setSessionArchived/getSessionArchived. |
| extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/chatSessionMetadataStoreImpl.ts | Implements archived persistence; getSessionParentId now requires kind to return a value; forked metadata writes archived: false. |
| extensions/copilot/src/extension/chatSessions/common/test/mockChatSessionMetadataStore.ts | Mock implementation of archived state and updated getSessionParentId signature. |
| extensions/copilot/src/extension/chatSessions/common/chatSessionWorkspaceFolderService.ts | Adds getAssociatedSessions(folderUri) to the workspace folder service interface. |
| extensions/copilot/src/extension/chatSessions/vscode-node/worktreeSharing.ts | New utility computing sibling sessions that block worktree deletion. |
| extensions/copilot/src/extension/chatSessions/vscode-node/copilotCLIChatSessions.ts (V2) | Persists archive state, gates worktree cleanup, and threads metadataStore into command registration; updates sessionParentId derivation. |
| extensions/copilot/src/extension/chatSessions/vscode-node/copilotCLIChatSessionsContribution.ts (V1) | Same gating for delete/bulk delete; updates sessionParentId derivation; threads metadataStore through registerCLIChatCommands. |
| extensions/copilot/src/extension/chatSessions/vscode-node/chatSessions.ts | Wires V1 archive-state handler and passes IChatSessionMetadataStore into registerCLIChatCommands for both V1 and V2. |
| extensions/copilot/src/extension/chatSessions/vscode-node/test/copilotCLIChatSessions.spec.ts | Tightens getSessionParentId mock typing for the new return type. |
| extensions/copilot/test/e2e/cli.stest.ts | Adds getAssociatedSessions stub to satisfy the extended IChatSessionWorkspaceFolderService interface. |
Copilot's findings
Comments suppressed due to low confidence (2)
extensions/copilot/src/extension/chatSessions/vscode-node/copilotCLIChatSessions.ts:298
- The archive-state handler (persist archived → check sibling worktrees → cleanup/recreate) is now duplicated nearly verbatim between
chatSessions.ts(V1 wiring) andcopilotCLIChatSessions.ts(V2 provider). Likewise,shouldKeepWorktreeForOtherSessionsis duplicated between the V1 (copilotCLIChatSessionsContribution.ts) and V2 (copilotCLIChatSessions.ts)registerCLIChatCommandsfunctions. Consider extracting both into a shared helper alongsidegetBlockingSiblingSessionsForFolderinworktreeSharing.tsso the two code paths cannot drift (e.g. fixes/log message changes need to be made in two places today).
This issue also appears on line 266 of the same file.
// Handle worktree cleanup/recreation when archive state changes
if (controller.onDidChangeChatSessionItemState) {
this._register(controller.onDidChangeChatSessionItemState(async (item) => {
const sessionId = SessionIdForCLI.parse(item.resource);
// Persist archived state first so worktree-sharing checks (delete/archive)
// can ignore archived siblings — their worktrees are reconstructed on
// un-archive via `recreateWorktreeOnUnarchive`.
try {
await this._metadataStore.setSessionArchived(sessionId, !!item.archived);
} catch (error) {
this.logService.error(`[CopilotCLI] Failed to persist archived state for session ${sessionId}:`, error);
}
if (item.archived) {
// Skip worktree cleanup if other live sessions still depend on this worktree.
const worktreePath = await this.copilotCLIWorktreeManagerService.getWorktreePath(sessionId);
if (worktreePath) {
const siblings = await getBlockingSiblingSessionsForFolder(worktreePath, sessionId, this._metadataStore, this._workspaceFolderService);
if (siblings.length > 0) {
this.logService.trace(`[CopilotCLI] Skipping worktree cleanup for archived session ${sessionId}: ${siblings.length} other session(s) still use the worktree`);
return;
}
}
try {
const result = await this.copilotCLIWorktreeManagerService.cleanupWorktreeOnArchive(sessionId);
this.logService.trace(`[CopilotCLI] Worktree cleanup for session ${sessionId}: ${result.cleaned ? 'cleaned' : result.reason}`);
} catch (error) {
this.logService.error(`[CopilotCLI] Failed to cleanup worktree for archived session ${sessionId}:`, error);
}
} else {
try {
const result = await this.copilotCLIWorktreeManagerService.recreateWorktreeOnUnarchive(sessionId);
this.logService.trace(`[CopilotCLI] Worktree recreation for session ${sessionId}: ${result.recreated ? 'recreated' : result.reason}`);
if (result.recreated) {
await this.refreshSession({ reason: 'update', sessionId });
}
} catch (error) {
this.logService.error(`[CopilotCLI] Failed to recreate worktree for unarchived session ${sessionId}:`, error);
}
}
}));
extensions/copilot/src/extension/chatSessions/vscode-node/copilotCLIChatSessions.ts:286
- When two (or more) sessions sharing a worktree are archived in close succession, both handlers can race: each persists its own
archived=true, then each runsgetBlockingSiblingSessionsForFolderand may still see the other as not yet archived (sincesetSessionArchivedfor the other has not completed) — resulting in both skippingcleanupWorktreeOnArchiveand the worktree being orphaned. Conversely, if both reads complete after both writes, both will see the other as archived and again skip cleanup. Consider serializing per-worktree, or having the last archive perform a sweep, so the worktree is reliably cleaned up when no live sibling remains.
try {
await this._metadataStore.setSessionArchived(sessionId, !!item.archived);
} catch (error) {
this.logService.error(`[CopilotCLI] Failed to persist archived state for session ${sessionId}:`, error);
}
if (item.archived) {
// Skip worktree cleanup if other live sessions still depend on this worktree.
const worktreePath = await this.copilotCLIWorktreeManagerService.getWorktreePath(sessionId);
if (worktreePath) {
const siblings = await getBlockingSiblingSessionsForFolder(worktreePath, sessionId, this._metadataStore, this._workspaceFolderService);
if (siblings.length > 0) {
this.logService.trace(`[CopilotCLI] Skipping worktree cleanup for archived session ${sessionId}: ${siblings.length} other session(s) still use the worktree`);
return;
}
}
try {
const result = await this.copilotCLIWorktreeManagerService.cleanupWorktreeOnArchive(sessionId);
this.logService.trace(`[CopilotCLI] Worktree cleanup for session ${sessionId}: ${result.cleaned ? 'cleaned' : result.reason}`);
} catch (error) {
this.logService.error(`[CopilotCLI] Failed to cleanup worktree for archived session ${sessionId}:`, error);
}
- Files reviewed: 10/10 changed files
- Comments generated: 3
Co-authored-by: DonJayamanne <1948812+DonJayamanne@users.noreply.github.com>
justschen
approved these changes
May 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
archivedproperty inChatSessionMetadataFileto track archived sessions.IChatSessionMetadataStoreinterface to include methods for setting and getting archived state.ChatSessionMetadataStoreandMockChatSessionMetadataStoreto handle archived sessions.getBlockingSiblingSessionsForFolderutility to identify sessions that share worktrees.Fixes #316560