Add new chat items to inProgressItem instead of the controller#309965
Add new chat items to inProgressItem instead of the controller#309965TylerLeonhardt merged 1 commit intomainfrom
Conversation
Since they have not yet been created on disk, we should keep these in the inProgress map so they're maintained across refreshes.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR changes how newly created (not yet persisted) chat session items are tracked so they survive refreshes by storing them in the in-progress map rather than immediately adding them to the controller’s main items collection.
Changes:
- Store newly created chat items in
_inProgressItemskeyed bynewSessionIdinstead of adding them tothis._controller.items.
Show a summary per file
| File | Description |
|---|---|
| extensions/copilot/src/extension/chatSessions/vscode-node/claudeChatSessionContentProvider.ts | Routes newly created (not-yet-on-disk) chat items into the in-progress tracking map to persist across refreshes. |
Copilot's findings
- Files reviewed: 1/1 changed files
- Comments generated: 2
| cwd: folder, | ||
| }; | ||
| this._controller.items.add(item); | ||
| this._inProgressItems.set(newSessionId, item); |
There was a problem hiding this comment.
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;
}
| @@ -438,7 +438,7 @@ export class ClaudeChatSessionItemController extends Disposable { | |||
| permissionMode, | |||
| cwd: folder, | |||
| }; | |||
There was a problem hiding this comment.
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.
| }; | |
| }; | |
| // 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`. |
Since they have not yet been created on disk, we should keep these in the inProgress map so they're maintained across refreshes.