From 83f7827968f1dd4e8c972924249293e0152f38b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:24:02 +0000 Subject: [PATCH 1/3] Initial plan From deb6ad5d75b8465beceaf6d48c21e1bdc7199bb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:39:11 +0000 Subject: [PATCH 2/3] Serialize resolveCommentThread operations to fix race condition When resolving multiple PR comments quickly in the webview, concurrent resolveCommentThread handlers would race: each fetches the full timeline after its mutation, and a stale response from an earlier call can overwrite a newer one. Serialize these operations using a promise queue so each mutation + timeline fetch completes before the next starts. Agent-Logs-Url: https://github.com/microsoft/vscode-pull-request-github/sessions/cbcba014-095c-41d9-a8bd-d041f599c908 Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/pullRequestOverview.ts | 32 ++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 19730b48a3..70bbed1468 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -64,6 +64,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel | undefined; + private _resolveCommentThreadQueue: Promise = Promise.resolve(); public static override async createOrShow( telemetry: ITelemetry, @@ -785,20 +786,25 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel) { - try { - if (message.args.toResolve) { - await this._item.resolveReviewThread(message.args.threadId); - } - else { - await this._item.unresolveReviewThread(message.args.threadId); + private resolveCommentThread(message: IRequestMessage<{ threadId: string, toResolve: boolean, thread: IComment[] }>) { + // Serialize resolve/unresolve operations so that concurrent calls don't race. + // Each call fetches the full timeline after its mutation, and without serialization + // a stale timeline response from an earlier call can overwrite a newer one. + this._resolveCommentThreadQueue = this._resolveCommentThreadQueue.then(async () => { + try { + if (message.args.toResolve) { + await this._item.resolveReviewThread(message.args.threadId); + } + else { + await this._item.unresolveReviewThread(message.args.threadId); + } + const timelineEvents = await this._getTimeline(); + this._replyMessage(message, timelineEvents); + } catch (e) { + vscode.window.showErrorMessage(e); + this._replyMessage(message, undefined); } - const timelineEvents = await this._getTimeline(); - this._replyMessage(message, timelineEvents); - } catch (e) { - vscode.window.showErrorMessage(e); - this._replyMessage(message, undefined); - } + }); } private checkoutPullRequest(message: IRequestMessage): void { From 930cc501a0f9bd92ad30861af23cd8aabb3f4d24 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:21:48 +0200 Subject: [PATCH 3/3] CCR feedback --- src/github/pullRequestOverview.ts | 35 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 70bbed1468..3abb26ada6 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -790,21 +790,28 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel { - try { - if (message.args.toResolve) { - await this._item.resolveReviewThread(message.args.threadId); - } - else { - await this._item.unresolveReviewThread(message.args.threadId); + // Normalize any prior rejection so one unexpected failure does not permanently + // block later resolve/unresolve requests from running. + this._resolveCommentThreadQueue = this._resolveCommentThreadQueue + .catch(error => { + Logger.error(`Resolve comment thread queue failed: ${formatError(error)}`, PullRequestOverviewPanel.ID); + }) + .then(async () => { + try { + if (message.args.toResolve) { + await this._item.resolveReviewThread(message.args.threadId); + } + else { + await this._item.unresolveReviewThread(message.args.threadId); + } + const timelineEvents = await this._getTimeline(); + this._replyMessage(message, timelineEvents); + } catch (e) { + Logger.error(`Failed to ${message.args.toResolve ? 'resolve' : 'unresolve'} comment thread: ${formatError(e)}`, PullRequestOverviewPanel.ID); + vscode.window.showErrorMessage(vscode.l10n.t('Failed to {0} comment thread: {1}', message.args.toResolve ? 'resolve' : 'unresolve', formatError(e))); + this._replyMessage(message, undefined); } - const timelineEvents = await this._getTimeline(); - this._replyMessage(message, timelineEvents); - } catch (e) { - vscode.window.showErrorMessage(e); - this._replyMessage(message, undefined); - } - }); + }); } private checkoutPullRequest(message: IRequestMessage): void {