Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ export class ClaudeChatSessionItemController extends Disposable {
permissionMode,
cwd: folder,
};
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This change encodes an important lifecycle rule ("items not yet created on disk must live in _inProgressItems"). Consider adding a short inline comment here (or a brief doc comment on _inProgressItems) describing when entries are added/removed and when they are promoted into the controller’s main items collection. This will help future changes avoid accidentally double-tracking or orphaning items.

Suggested change
};
};
// Newly created items live here until the backing session exists on disk; once persisted,
// they are removed from `_inProgressItems` and promoted into `this._controller.items`.

Copilot uses AI. Check for mistakes.
this._controller.items.add(item);
this._inProgressItems.set(newSessionId, item);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

By no longer adding the item to this._controller.items, any logic that relies on the controller’s main item collection (e.g., UI listing, disposal, or persistence transitions) may not see this item until it’s written to disk. To avoid stale entries and potential memory/resource leaks, ensure there is a guaranteed transition path that (a) moves the item from _inProgressItems into this._controller.items once it’s persisted, and (b) removes it from _inProgressItems on completion, cancellation, or failure (ideally in a finally/cleanup path).

See below for a potential fix:

		const cleanupInProgressItem = (sessionId: string, removeTransientControllerItem = false) => {
			const inProgressItem = this._inProgressItems.get(sessionId);
			if (!inProgressItem) {
				return;
			}

			this._inProgressItems.delete(sessionId);

			if (removeTransientControllerItem) {
				const sessionUri = ClaudeSessionUri.forSessionId(sessionId);
				if (this._controller.items.get(sessionUri) === inProgressItem) {
					this._controller.items.delete(sessionUri);
				}
			}
		};

		const reconcileInProgressItems = () => {
			for (const [sessionId, inProgressItem] of this._inProgressItems) {
				const sessionUri = ClaudeSessionUri.forSessionId(sessionId);
				const currentItem = this._controller.items.get(sessionUri);

				if (currentItem && currentItem !== inProgressItem) {
					this._inProgressItems.delete(sessionId);
				}
			}
		};

		this._controller = this._register(vscode.chat.createChatSessionItemController(
			ClaudeSessionUri.scheme,
			async () => {
				try {
					await this._refreshItems(CancellationToken.None);
				} finally {
					reconcileInProgressItems();
				}
			}
		));

		this._controller.newChatSessionItemHandler = async (context, token) => {
			const newSessionId = generateUuid();

			try {
				const item = this._controller.createChatSessionItem(
					ClaudeSessionUri.forSessionId(newSessionId),
					context.request.prompt,
				);
				item.iconPath = new vscode.ThemeIcon('claude');
				item.timing = { created: Date.now() };
				const permissionModeOptionValue = context.sessionOptions?.find(o => o.optionId === PERMISSION_MODE_OPTION_ID)?.value;
				const permissionMode = typeof permissionModeOptionValue === 'string' ? permissionModeOptionValue : permissionModeOptionValue?.id;
				const folderOptionValue = context.sessionOptions?.find(o => o.optionId === FOLDER_OPTION_ID)?.value;
				const folder = typeof folderOptionValue === 'string'
					? URI.file(folderOptionValue)
					: folderOptionValue?.id
						? URI.file(folderOptionValue.id)
						: undefined;
				item.metadata = {
					permissionMode,
					cwd: folder,
				};

				this._controller.items.add(item);
				this._inProgressItems.set(newSessionId, item);
				token.onCancellationRequested(() => cleanupInProgressItem(newSessionId, true));

				return item;
			} catch (error) {
				cleanupInProgressItem(newSessionId, true);
				throw error;
			}

Copilot uses AI. Check for mistakes.
return item;
};

Expand Down
Loading