From 9defc98a40f6a71480cf824419e9a5ec6345cf49 Mon Sep 17 00:00:00 2001 From: boygeniusnr1 Date: Sat, 29 Aug 2026 21:31:53 +0800 Subject: [PATCH 1/2] Handle stringified forwarded chatError in getChatErrorDetailsFromMeta\n\nParse JSON when _meta.chatError is a string so friendly forwarded messages render instead of falling back to generic errors. Add focused unit test.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../contrib/chat/common/chatErrorMessages.ts | 14 ++++++- .../chatErrorMessages.stringified.test.ts | 37 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.test.ts diff --git a/src/vs/workbench/contrib/chat/common/chatErrorMessages.ts b/src/vs/workbench/contrib/chat/common/chatErrorMessages.ts index b2733963facb9e..87bd4366fc91d4 100644 --- a/src/vs/workbench/contrib/chat/common/chatErrorMessages.ts +++ b/src/vs/workbench/contrib/chat/common/chatErrorMessages.ts @@ -354,7 +354,19 @@ function isForwardedChatError(value: unknown): value is IForwardedChatError { */ export function getChatErrorDetailsFromMeta(error: ErrorInfo | undefined, context?: IChatErrorContext): IChatResponseErrorDetails | undefined { const meta = error?._meta; - const chatError = meta?.chatError; + let chatError = meta?.chatError; + // In some hosts the forwarded chat error may be JSON-stringified. Accept + // either the raw object or a stringified payload and try to parse it so we + // can render friendly messages instead of falling back to generic errors. + if (typeof chatError === 'string') { + try { + chatError = JSON.parse(chatError); + } catch { + // If parsing fails, fall back to undefined so callers use their + // existing generic handling. + return undefined; + } + } if (!isForwardedChatError(chatError)) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.test.ts b/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.test.ts new file mode 100644 index 00000000000000..a44998464ee4b5 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.test.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { ChatFetchResponseType, getChatErrorDetailsFromMeta } from '../../common/chatErrorMessages.js'; +import { ChatErrorLevel } from '../../common/chatService/chatService.js'; +import type { ErrorInfo } from '../../../../../platform/agentHost/common/state/protocol/state.js'; + +function errorInfo(meta: Record | undefined): ErrorInfo { + return { errorType: 'e', message: 'm', _meta: meta }; +} + +suite('ChatErrorMessages (stringified payload)', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('parses stringified forwarded chatError', () => { + const payload = JSON.stringify({ + fetchError: { + type: ChatFetchResponseType.RateLimited, + retryAfter: 60, + capiError: { code: 'user_global_rate_limited', message: 'slow down' }, + }, + copilotPlan: 'free', + }); + + const details = getChatErrorDetailsFromMeta(errorInfo({ chatError: payload })); + assert.deepStrictEqual(details, { + code: ChatFetchResponseType.RateLimited, + message: 'You\'ve hit your session rate limit. Please upgrade your plan or wait 60 seconds for your limit to reset. [Learn More](https://aka.ms/github-copilot-rate-limit-error)', + level: ChatErrorLevel.Info, + isRateLimited: true, + }); + }); +}); From e8b56fc7fd083755fd70b86dcbee5cbd408c7844 Mon Sep 17 00:00:00 2001 From: boygeniusnr1 Date: Sun, 30 Aug 2026 08:15:23 +0800 Subject: [PATCH 2/2] tests(chat): ensure malformed stringified forwarded chatError returns undefined (safe fallback)\n\nAdd unit test verifying invalid JSON in _meta.chatError is handled without throwing, preserving generic error fallback.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...rrorMessages.stringified.malformed.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.malformed.test.ts diff --git a/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.malformed.test.ts b/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.malformed.test.ts new file mode 100644 index 00000000000000..a3e25930b60275 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/chatErrorMessages.stringified.malformed.test.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { getChatErrorDetailsFromMeta } from '../../common/chatErrorMessages.js'; +import type { ErrorInfo } from '../../../../../platform/agentHost/common/state/protocol/state.js'; + +function errorInfo(meta: Record | undefined): ErrorInfo { + return { errorType: 'e', message: 'm', _meta: meta }; +} + +suite('ChatErrorMessages (malformed stringified payload)', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('invalid JSON in forwarded chatError returns undefined (safe fallback)', () => { + // Malformed JSON string should not throw; the helper should return undefined + // so callers fall back to generic error handling. + const payload = '{ fetchError: { type: "rateLimited", retryAfter: 60 }'; // missing closing brace + const details = getChatErrorDetailsFromMeta(errorInfo({ chatError: payload })); + assert.strictEqual(details, undefined); + }); +});