Skip to content

Add archived state management and worktree sharing for chat sessions#316561

Merged
DonJayamanne merged 2 commits into
mainfrom
don/forked-handsome-viper
May 15, 2026
Merged

Add archived state management and worktree sharing for chat sessions#316561
DonJayamanne merged 2 commits into
mainfrom
don/forked-handsome-viper

Conversation

@DonJayamanne
Copy link
Copy Markdown
Contributor

  • 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.

Fixes #316560

- 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.
Copilot AI review requested due to automatic review settings May 15, 2026 04:04
@DonJayamanne DonJayamanne self-assigned this May 15, 2026
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

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 archived field on ChatSessionMetadataFile, plus setSessionArchived / getSessionArchived on the metadata store and its mock.
  • New getBlockingSiblingSessionsForFolder utility and IChatSessionWorkspaceFolderService.getAssociatedSessions, used to gate worktree cleanup on archive and on single/bulk delete in both V1 and V2 CLI command paths.
  • getSessionParentId now returns { parentSessionId, kind: 'forked' | 'sub-session' }, and only 'sub-session' is surfaced as the UI parent; archive‑state changes persist archived and 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) and copilotCLIChatSessions.ts (V2 provider). Likewise, shouldKeepWorktreeForOtherSessions is duplicated between the V1 (copilotCLIChatSessionsContribution.ts) and V2 (copilotCLIChatSessions.ts) registerCLIChatCommands functions. Consider extracting both into a shared helper alongside getBlockingSiblingSessionsForFolder in worktreeSharing.ts so 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 runs getBlockingSiblingSessionsForFolder and may still see the other as not yet archived (since setSessionArchived for the other has not completed) — resulting in both skipping cleanupWorktreeOnArchive and 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>
@DonJayamanne DonJayamanne marked this pull request as ready for review May 15, 2026 05:20
@DonJayamanne DonJayamanne enabled auto-merge (squash) May 15, 2026 05:20
@DonJayamanne DonJayamanne merged commit d1e403e into main May 15, 2026
26 checks passed
@DonJayamanne DonJayamanne deleted the don/forked-handsome-viper branch May 15, 2026 05:21
@vs-code-engineering vs-code-engineering Bot added this to the 1.121.0 milestone May 15, 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.

Proper handling of forked session archives and enable for Stable

4 participants