From 25ba4beb0dc8b1b8cdf04dcacde887508f6bcbbf Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Fri, 29 May 2026 16:10:48 +0800 Subject: [PATCH 01/13] add more telemetry for install-tsp-compiler scenario --- .../src/vscode-cmd/install-tsp-compiler.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/typespec-vscode/src/vscode-cmd/install-tsp-compiler.ts b/packages/typespec-vscode/src/vscode-cmd/install-tsp-compiler.ts index 3ef10fa1e1e..d2c04f8d01f 100644 --- a/packages/typespec-vscode/src/vscode-cmd/install-tsp-compiler.ts +++ b/packages/typespec-vscode/src/vscode-cmd/install-tsp-compiler.ts @@ -1,3 +1,4 @@ +import { inspect } from "util"; import logger from "../log/logger.js"; import telemetryClient from "../telemetry/telemetry-client.js"; import { TelemetryEventName } from "../telemetry/telemetry-event.js"; @@ -11,6 +12,7 @@ export async function installCompilerGlobally( TelemetryEventName.InstallGlobalCompilerCli, async (tel) => { const showPopup = args?.silentMode !== true; + tel.lastStep = "Call installCompilerWithUi"; const result = await installCompilerWithUi( { confirmNeeded: args?.confirm !== false, @@ -20,13 +22,26 @@ export async function installCompilerGlobally( [] /*localPath, empty for global*/, ); if (result.code === ResultCode.Success) { + tel.lastStep = "Compiler installed successfully"; logger.info(`Compiler installed successfully`, [], { showPopup }); - } else if (result.code === ResultCode.Fail || result.code === ResultCode.Timeout) { - logger.error( - `Installing compiler ${result.code === ResultCode.Fail ? "failed" : "timeout"}. Please check previous logs for details`, - [], - { showPopup }, - ); + } else if (result.code === ResultCode.Cancelled) { + tel.lastStep = "User cancelled installation"; + } else if (result.code === ResultCode.Timeout) { + tel.lastStep = "Installation timeout"; + telemetryClient.logOperationDetailTelemetry(tel.activityId, { + error: `Installing compiler globally timeout`, + }); + logger.error(`Installing compiler timeout. Please check previous logs for details`, [], { + showPopup, + }); + } else if (result.code === ResultCode.Fail) { + tel.lastStep = "Installation failed"; + telemetryClient.logOperationDetailTelemetry(tel.activityId, { + error: `Installing compiler globally failed: ${inspect(result.details)}`, + }); + logger.error(`Installing compiler failed. Please check previous logs for details`, [], { + showPopup, + }); } return result; }, From dd992221d23c31ba444dc7f46abeffc0ab5d888d Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Fri, 29 May 2026 20:45:40 +0800 Subject: [PATCH 02/13] improve telemetry for openapi3-preview --- .../src/vscode-cmd/openapi3-preview.ts | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/packages/typespec-vscode/src/vscode-cmd/openapi3-preview.ts b/packages/typespec-vscode/src/vscode-cmd/openapi3-preview.ts index a5f9b55416f..a74651d6877 100644 --- a/packages/typespec-vscode/src/vscode-cmd/openapi3-preview.ts +++ b/packages/typespec-vscode/src/vscode-cmd/openapi3-preview.ts @@ -7,7 +7,7 @@ import { getBaseFileName, getDirectoryPath, joinPaths } from "../path-utils.js"; import telemetryClient from "../telemetry/telemetry-client.js"; import { OperationTelemetryEvent } from "../telemetry/telemetry-event.js"; import { TspLanguageClient } from "../tsp-language-client.js"; -import { ResultCode } from "../types.js"; +import { Result, ResultCode } from "../types.js"; import { getEntrypointTspFile, TraverseMainTspFileInWorkspace } from "../typespec-utils.js"; import { createTempDir, throttle } from "../utils.js"; @@ -136,13 +136,13 @@ async function loadOpenApi3PreviewPanel( } else { const getOpenApi3OutputFilePath = async ( selectOutput: boolean, - ): Promise => { + ): Promise> => { return await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: "Loading OpenAPI3 files...", }, - async (): Promise => { + async (): Promise> => { const srcFolder = getDirectoryPath(mainTspFile); const outputFolder = await getOutputFolder(mainTspFile, tmpRoot); if (!outputFolder) { @@ -153,7 +153,7 @@ async function loadOpenApi3PreviewPanel( telemetryClient.logOperationDetailTelemetry(tel.activityId, { error: "Failed to create temporary folder for OpenAPI3 files", }); - return undefined; + return { code: ResultCode.Fail }; } await clearOutputFolder(outputFolder); @@ -163,27 +163,32 @@ async function loadOpenApi3PreviewPanel( "Failed to generate OpenAPI3 files.", result?.stderr ? [result.stderr] : [], ); - return; - } else { - return await selectAndGetOpenApi3FilePath( - mainTspFile, - outputFolder, - selectOutput, - context, - ); + telemetryClient.logOperationDetailTelemetry(tel.activityId, { + error: `Failed to compile OpenAPI3: exitCode=${result?.exitCode ?? "N/A"}, stderr=${result?.stderr ?? "N/A"}`, + }); + return { code: ResultCode.Fail }; + } + const filePath = await selectAndGetOpenApi3FilePath( + mainTspFile, + outputFolder, + selectOutput, + context, + ); + if (filePath === undefined) { + return { code: ResultCode.Cancelled }; } + return { code: ResultCode.Success, value: filePath }; }, ); }; - const filePath = await getOpenApi3OutputFilePath(true); - if (filePath === undefined) { - telemetryClient.logOperationDetailTelemetry(tel.activityId, { - error: "Failed to get generated OpenAPI3 file", - }); - tel.lastStep = "Get OpenAPI3 output"; - return ResultCode.Cancelled; + const outputResult = await getOpenApi3OutputFilePath(true); + if (outputResult.code !== ResultCode.Success) { + tel.lastStep = + outputResult.code === ResultCode.Fail ? "Compile OpenAPI3 failed" : "Get OpenAPI3 output"; + return outputResult.code; } + const filePath = outputResult.value; const panel = vscode.window.createWebviewPanel( "webview", @@ -199,11 +204,11 @@ async function loadOpenApi3PreviewPanel( const watch = vscode.workspace.createFileSystemWatcher("**/*.{tsp}"); const throttledChangeHandler = throttle(async () => { - const outputFilePath = await getOpenApi3OutputFilePath(false); - if (outputFilePath) { + const refreshResult = await getOpenApi3OutputFilePath(false); + if (refreshResult.code === ResultCode.Success) { void panel.webview.postMessage({ command: "load", - param: panel.webview.asWebviewUri(vscode.Uri.file(outputFilePath)).toString(), + param: panel.webview.asWebviewUri(vscode.Uri.file(refreshResult.value)).toString(), }); } }, 1000); @@ -233,6 +238,7 @@ async function loadOpenApi3PreviewPanel( loadHtml(context.extensionUri, panel); } + tel.lastStep = "Preview panel opened"; return ResultCode.Success; } From 752a725fb4837e9d927eae2d392c25c9e80d8ac6 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Fri, 29 May 2026 20:46:07 +0800 Subject: [PATCH 03/13] improve telemetry for openapi3-preview --- packages/typespec-vscode/src/vscode-cmd/openapi3-preview.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/typespec-vscode/src/vscode-cmd/openapi3-preview.ts b/packages/typespec-vscode/src/vscode-cmd/openapi3-preview.ts index a74651d6877..681eeae1e93 100644 --- a/packages/typespec-vscode/src/vscode-cmd/openapi3-preview.ts +++ b/packages/typespec-vscode/src/vscode-cmd/openapi3-preview.ts @@ -134,9 +134,7 @@ async function loadOpenApi3PreviewPanel( }); panel.reveal(); } else { - const getOpenApi3OutputFilePath = async ( - selectOutput: boolean, - ): Promise> => { + const getOpenApi3OutputFilePath = async (selectOutput: boolean): Promise> => { return await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, From 64220f64bffe9a3652999c060758284804ecd0e8 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Fri, 29 May 2026 22:37:00 +0800 Subject: [PATCH 04/13] improve start server telemetry --- packages/typespec-vscode/src/extension.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 4746b78288a..c47023e9ae8 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -316,6 +316,7 @@ export async function activate(context: ExtensionContext) { } // client will be undefined only when we can't find compiler locally or globally // otherwise, the client should always be created though the start command may fail which is a different case + ssTel.lastStep = "Compiler not found (prompting to install)"; const choice: "Yes" | "Ignore" | undefined = await vscode.window.showWarningMessage( "No TypeSpec compiler found which is required to start TypeSpec language server. Do you want to install TypeSpec compiler?", "Yes", @@ -355,6 +356,8 @@ export async function activate(context: ExtensionContext) { { showPopup: true }, ); ssTel.lastStep = "Failed to install TypeSpec compiler."; + } else { + ssTel.lastStep = "Install TypeSpec compiler cancelled."; } return installResult.code; }, From a3ef8a5d630896f140c457f9bf305c50e6363ef2 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Mon, 1 Jun 2026 12:22:40 +0800 Subject: [PATCH 05/13] more error message for node/tsp not in PATH --- .../src/tsp-executable-resolver.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/typespec-vscode/src/tsp-executable-resolver.ts b/packages/typespec-vscode/src/tsp-executable-resolver.ts index 95c160e49ea..15e8666a56f 100644 --- a/packages/typespec-vscode/src/tsp-executable-resolver.ts +++ b/packages/typespec-vscode/src/tsp-executable-resolver.ts @@ -8,6 +8,7 @@ import { SettingName } from "./types.js"; import { checkInstalledExecutable, checkInstalledNode, + checkInstalledTspCli, isFile, loadModule, useShellInExec, @@ -159,12 +160,32 @@ export async function resolveTypeSpecServer( }); return { command: "node", args: [serverPath, ...args], options }; } else { - // otherwise the local compiler should be installed by standalone tsp cli - logger.debug("Start tsp server using standalone tsp cli"); + const tspCliPath = await checkInstalledTspCli(); + if (tspCliPath.length > 0) { + logger.debug("Start tsp server using standalone tsp cli"); + telemetryClient.logOperationDetailTelemetry(activityId, { + compilerStartType: "standalone-tsp-cli", + }); + return { command: "tsp", args: ["--server", serverPath, ...args], options }; + } + // Neither node nor tsp is on PATH. + logger.error( + [ + `TypeSpec compiler was found at '${serverPath}', but it cannot be started because neither 'node' nor 'tsp' is available in PATH.`, + "This commonly happens when Node.js is installed via a version manager (nvm, fnm, volta) whose PATH is not inherited by VS Code.", + "To fix this, try one of the following:", + " - Launch VS Code from a terminal where 'node' is available (e.g. run 'code .' after activating nvm).", + " - Set the 'typespec.tsp-server.path' setting to the full path of your tsp-server.js file.", + " - Install Node.js system-wide so it's available to all processes.", + ].join("\n"), + [], + { showPopup: true, showOutput: true }, + ); telemetryClient.logOperationDetailTelemetry(activityId, { - compilerStartType: "standalone-tsp-cli", + compilerStartType: "not-available", + error: "Neither node nor tsp is available in PATH. Compiler found but cannot be started.", }); - return { command: "tsp", args: ["--server", serverPath, ...args], options }; + return undefined; } } From dd64740061d67ac535c3370df86a34d55a9aab42 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Mon, 1 Jun 2026 16:37:09 +0800 Subject: [PATCH 06/13] add lastStep for config change --- packages/typespec-vscode/src/extension.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index c47023e9ae8..6a33d931fff 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -245,6 +245,7 @@ export async function activate(context: ExtensionContext) { await telemetryClient.doOperationWithTelemetry( TelemetryEventName.ServerPathSettingChanged, async (tel) => { + tel.lastStep = "Recreate LSP client for path change"; return await recreateLSPClient(context, tel.activityId); }, undefined, From 49d2eea605596f701aca0b39d15ddce3b5c03fb1 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Mon, 1 Jun 2026 18:29:53 +0800 Subject: [PATCH 07/13] wrap unhandled error to get more information for troubleshooting --- packages/compiler/src/server/serverlib.ts | 87 +++++++++++++++-------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index b23b6e29913..bfb9c54f261 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -1,3 +1,4 @@ +import { inspect } from "util"; import { TextDocument } from "vscode-languageserver-textdocument"; import { CodeAction, @@ -182,6 +183,29 @@ export function createServer( let isInitialized = false; let pendingMessages: ServerLog[] = []; + /** + * Wraps an LSP request handler to preserve the server-side error details when + * a handler crashes. By default, the JSON-RPC layer (vscode-languageserver) catches + * handler errors and creates a new ResponseError using only `error.message`, discarding + * the original stack trace. This means the client-side telemetry (unhandled_error_message / + * unhandled_error_stack) only sees the client-side message handling stack, not the actual + * crash location in the compiler/server code. + * + * This wrapper catches the error first and re-throws a new Error whose message includes + * the full original error details (via `inspect(e)`, which expands the stack, properties, + * etc.). The JSON-RPC layer then forwards this enriched message to the client, making + * the server-side crash location visible in telemetry for investigation. + */ + function wrapUnhandledError any>(name: string, fn: T): T { + return (async (...args: any[]) => { + try { + return await fn(...args); + } catch (e) { + throw new Error(`[${name}] ${inspect(e)}`, { cause: e }); + } + }) as T; + } + return { get pendingMessages() { return pendingMessages; @@ -189,37 +213,40 @@ export function createServer( get workspaceFolders() { return workspaceFolders; }, - compile, - initialize, - initialized, - workspaceFoldersChanged, - watchedFilesChanged, - formatDocument, - gotoDefinition, - documentClosed, - documentOpened, - complete, - findReferences, - findDocumentHighlight, - prepareRename, - rename, - renameFiles, - getSemanticTokens: getSemanticTokensForDocument, - buildSemanticTokens, - checkChange, - getFoldingRanges, - getHover, - getSignatureHelp, - getDocumentSymbols, - getCodeActions, - resolveCodeAction, + compile: wrapUnhandledError("compile", compile), + initialize: wrapUnhandledError("initialize", initialize), + initialized: wrapUnhandledError("initialized", initialized), + workspaceFoldersChanged: wrapUnhandledError("workspaceFoldersChanged", workspaceFoldersChanged), + watchedFilesChanged: wrapUnhandledError("watchedFilesChanged", watchedFilesChanged), + formatDocument: wrapUnhandledError("formatDocument", formatDocument), + gotoDefinition: wrapUnhandledError("gotoDefinition", gotoDefinition), + documentClosed: wrapUnhandledError("documentClosed", documentClosed), + documentOpened: wrapUnhandledError("documentOpened", documentOpened), + complete: wrapUnhandledError("complete", complete), + findReferences: wrapUnhandledError("findReferences", findReferences), + findDocumentHighlight: wrapUnhandledError("findDocumentHighlight", findDocumentHighlight), + prepareRename: wrapUnhandledError("prepareRename", prepareRename), + rename: wrapUnhandledError("rename", rename), + renameFiles: wrapUnhandledError("renameFiles", renameFiles), + getSemanticTokens: wrapUnhandledError("getSemanticTokens", getSemanticTokensForDocument), + buildSemanticTokens: wrapUnhandledError("buildSemanticTokens", buildSemanticTokens), + checkChange: wrapUnhandledError("checkChange", checkChange), + getFoldingRanges: wrapUnhandledError("getFoldingRanges", getFoldingRanges), + getHover: wrapUnhandledError("getHover", getHover), + getSignatureHelp: wrapUnhandledError("getSignatureHelp", getSignatureHelp), + getDocumentSymbols: wrapUnhandledError("getDocumentSymbols", getDocumentSymbols), + getCodeActions: wrapUnhandledError("getCodeActions", getCodeActions), + resolveCodeAction: wrapUnhandledError("resolveCodeAction", resolveCodeAction), log, - reportDiagnostics, - - getInitProjectContext, - validateInitProjectTemplate, - initProject, - internalCompile, + reportDiagnostics: wrapUnhandledError("reportDiagnostics", reportDiagnostics), + + getInitProjectContext: wrapUnhandledError("getInitProjectContext", getInitProjectContext), + validateInitProjectTemplate: wrapUnhandledError( + "validateInitProjectTemplate", + validateInitProjectTemplate, + ), + initProject: wrapUnhandledError("initProject", initProject), + internalCompile: wrapUnhandledError("internalCompile", internalCompile), }; async function initialize(params: InitializeParams): Promise { From 455525f75cf16ca0433f4d29e526c3e6382172ee Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Mon, 1 Jun 2026 18:33:03 +0800 Subject: [PATCH 08/13] improve comment --- packages/compiler/src/server/serverlib.ts | 33 +++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index bfb9c54f261..d6481bacf9a 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -184,17 +184,34 @@ export function createServer( let pendingMessages: ServerLog[] = []; /** - * Wraps an LSP request handler to preserve the server-side error details when - * a handler crashes. By default, the JSON-RPC layer (vscode-languageserver) catches - * handler errors and creates a new ResponseError using only `error.message`, discarding - * the original stack trace. This means the client-side telemetry (unhandled_error_message / - * unhandled_error_stack) only sees the client-side message handling stack, not the actual - * crash location in the compiler/server code. + * Wraps an LSP handler to preserve the server-side error details when it crashes. + * + * By default, the JSON-RPC layer (vscode-languageserver) catches handler errors and + * creates a new ResponseError using only `error.message`, discarding the original stack + * trace. On the client side, the telemetry framework then captures this as an unhandled + * error, but the `unhandled_error_stack` only shows the client-side message handling code: + * + * ``` + * Error: Request textDocument/hover failed with message: Cannot read properties of undefined (reading 'kind') + * at handleResponse (extension.cjs:2104:40) // <-- client-side LSP message handler + * at handleMessage (extension.cjs:1914:11) + * at processMessageQueue (extension.cjs:1929:13) + * at Immediate. (extension.cjs:1905:11) + * ``` + * + * The actual server-side crash location (e.g., in the checker or parser) is completely lost. * * This wrapper catches the error first and re-throws a new Error whose message includes * the full original error details (via `inspect(e)`, which expands the stack, properties, - * etc.). The JSON-RPC layer then forwards this enriched message to the client, making - * the server-side crash location visible in telemetry for investigation. + * etc.). The JSON-RPC layer then forwards this enriched message to the client, so the + * telemetry `unhandled_error_message` will contain the server-side crash location: + * + * ``` + * [getHover] TypeError: Cannot read properties of undefined (reading 'kind') + * at Checker.getTypeForNode (checker.ts:1234:15) // <-- actual crash location + * at getHover (serverlib.ts:826:52) + * ... + * ``` */ function wrapUnhandledError any>(name: string, fn: T): T { return (async (...args: any[]) => { From f706a9cc0cec904d7df8650bfccbd46f798dcdff Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Mon, 1 Jun 2026 18:38:34 +0800 Subject: [PATCH 09/13] add changelog entries --- .chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md | 7 +++++++ .chronus/changes/tel-improve-vscode-2026-6-1-19-0-0.md | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 .chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md create mode 100644 .chronus/changes/tel-improve-vscode-2026-6-1-19-0-0.md diff --git a/.chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md b/.chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md new file mode 100644 index 00000000000..a14452432bd --- /dev/null +++ b/.chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Wrapped LSP server handlers with `wrapUnhandledError` to preserve server-side stack traces in error messages forwarded to the client. Previously, the JSON-RPC layer discarded the original stack trace, making unhandled errors in telemetry opaque. diff --git a/.chronus/changes/tel-improve-vscode-2026-6-1-19-0-0.md b/.chronus/changes/tel-improve-vscode-2026-6-1-19-0-0.md new file mode 100644 index 00000000000..f10896d25eb --- /dev/null +++ b/.chronus/changes/tel-improve-vscode-2026-6-1-19-0-0.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "typespec-vscode" +--- + +Improved telemetry instrumentation for `install-global-compiler-cli`, `preview-openapi3`, `start-server`, and `server-path-changed` events by adding missing `lastStep` tracking and error detail logging. Added actionable error message when compiler is found but neither `node` nor `tsp` is available on PATH, guiding users to fix common nvm/fnm/volta configuration issues. From 74e380e8c6b75617aaa6505041169cecac0600cc Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Mon, 1 Jun 2026 20:42:00 +0800 Subject: [PATCH 10/13] fix: remove util import, improve error serialization in wrapUnhandledError --- packages/compiler/src/server/serverlib.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index d6481bacf9a..21a853098da 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -1,4 +1,3 @@ -import { inspect } from "util"; import { TextDocument } from "vscode-languageserver-textdocument"; import { CodeAction, @@ -202,8 +201,8 @@ export function createServer( * The actual server-side crash location (e.g., in the checker or parser) is completely lost. * * This wrapper catches the error first and re-throws a new Error whose message includes - * the full original error details (via `inspect(e)`, which expands the stack, properties, - * etc.). The JSON-RPC layer then forwards this enriched message to the client, so the + * the full original error details (stack trace for Error instances, String() for others). + * The JSON-RPC layer then forwards this enriched message to the client, so the * telemetry `unhandled_error_message` will contain the server-side crash location: * * ``` @@ -218,7 +217,21 @@ export function createServer( try { return await fn(...args); } catch (e) { - throw new Error(`[${name}] ${inspect(e)}`, { cause: e }); + if (e instanceof Error) { + const detail = e.stack ? `${e.message}\n${e.stack}` : e.message; + throw new Error(`[${name}] ${detail}`, { cause: e }); + } else if (typeof e === "string") { + throw new Error(`[${name}] ${e}`, { cause: e }); + } else if (typeof e === "object" && e !== null) { + let detail: string; + try { + detail = JSON.stringify(e); + } catch { + throw e; + } + throw new Error(`[${name}] ${detail}`, { cause: e }); + } + throw e; } }) as T; } From 9da318f7c55356293291573c071137ea8cd3346d Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Tue, 2 Jun 2026 11:31:56 +0800 Subject: [PATCH 11/13] have a fallback --- packages/typespec-vscode/src/tsp-executable-resolver.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/typespec-vscode/src/tsp-executable-resolver.ts b/packages/typespec-vscode/src/tsp-executable-resolver.ts index 15e8666a56f..6845ab55af2 100644 --- a/packages/typespec-vscode/src/tsp-executable-resolver.ts +++ b/packages/typespec-vscode/src/tsp-executable-resolver.ts @@ -168,7 +168,9 @@ export async function resolveTypeSpecServer( }); return { command: "tsp", args: ["--server", serverPath, ...args], options }; } - // Neither node nor tsp is on PATH. + // Neither node nor tsp is on PATH. Show an actionable error but still try tsp + // as a last resort — it may work in some environments where `which` fails but + // the shell can still resolve the command. logger.error( [ `TypeSpec compiler was found at '${serverPath}', but it cannot be started because neither 'node' nor 'tsp' is available in PATH.`, @@ -182,10 +184,10 @@ export async function resolveTypeSpecServer( { showPopup: true, showOutput: true }, ); telemetryClient.logOperationDetailTelemetry(activityId, { - compilerStartType: "not-available", + compilerStartType: "standalone-tsp-cli-fallback", error: "Neither node nor tsp is available in PATH. Compiler found but cannot be started.", }); - return undefined; + return { command: "tsp", args: ["--server", serverPath, ...args], options }; } } From fc17617d5ba54f522f997141949df4fd2dcdf1f5 Mon Sep 17 00:00:00 2001 From: Rodge Fu Date: Wed, 3 Jun 2026 09:52:20 +0800 Subject: [PATCH 12/13] Update .chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md Co-authored-by: Timothee Guerin --- .chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md b/.chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md index a14452432bd..dce8917bbe6 100644 --- a/.chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md +++ b/.chronus/changes/tel-improve-compiler-2026-6-1-19-0-0.md @@ -4,4 +4,4 @@ packages: - "@typespec/compiler" --- -Wrapped LSP server handlers with `wrapUnhandledError` to preserve server-side stack traces in error messages forwarded to the client. Previously, the JSON-RPC layer discarded the original stack trace, making unhandled errors in telemetry opaque. +[Language Server] Wrapped LSP server handlers with `wrapUnhandledError` to preserve server-side stack traces in error messages forwarded to the client. Previously, the JSON-RPC layer discarded the original stack trace, making unhandled errors in telemetry opaque. From 288a78f0e79f049420b552585cd5fab660fa92c3 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Wed, 3 Jun 2026 09:59:38 +0800 Subject: [PATCH 13/13] use fn.name instead of passing string --- packages/compiler/src/server/serverlib.ts | 66 +++++++++++------------ 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 21a853098da..e34c5645fd5 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -212,7 +212,8 @@ export function createServer( * ... * ``` */ - function wrapUnhandledError any>(name: string, fn: T): T { + function wrapUnhandledError any>(fn: T): T { + const name = fn.name || "anonymous"; return (async (...args: any[]) => { try { return await fn(...args); @@ -243,40 +244,37 @@ export function createServer( get workspaceFolders() { return workspaceFolders; }, - compile: wrapUnhandledError("compile", compile), - initialize: wrapUnhandledError("initialize", initialize), - initialized: wrapUnhandledError("initialized", initialized), - workspaceFoldersChanged: wrapUnhandledError("workspaceFoldersChanged", workspaceFoldersChanged), - watchedFilesChanged: wrapUnhandledError("watchedFilesChanged", watchedFilesChanged), - formatDocument: wrapUnhandledError("formatDocument", formatDocument), - gotoDefinition: wrapUnhandledError("gotoDefinition", gotoDefinition), - documentClosed: wrapUnhandledError("documentClosed", documentClosed), - documentOpened: wrapUnhandledError("documentOpened", documentOpened), - complete: wrapUnhandledError("complete", complete), - findReferences: wrapUnhandledError("findReferences", findReferences), - findDocumentHighlight: wrapUnhandledError("findDocumentHighlight", findDocumentHighlight), - prepareRename: wrapUnhandledError("prepareRename", prepareRename), - rename: wrapUnhandledError("rename", rename), - renameFiles: wrapUnhandledError("renameFiles", renameFiles), - getSemanticTokens: wrapUnhandledError("getSemanticTokens", getSemanticTokensForDocument), - buildSemanticTokens: wrapUnhandledError("buildSemanticTokens", buildSemanticTokens), - checkChange: wrapUnhandledError("checkChange", checkChange), - getFoldingRanges: wrapUnhandledError("getFoldingRanges", getFoldingRanges), - getHover: wrapUnhandledError("getHover", getHover), - getSignatureHelp: wrapUnhandledError("getSignatureHelp", getSignatureHelp), - getDocumentSymbols: wrapUnhandledError("getDocumentSymbols", getDocumentSymbols), - getCodeActions: wrapUnhandledError("getCodeActions", getCodeActions), - resolveCodeAction: wrapUnhandledError("resolveCodeAction", resolveCodeAction), + compile: wrapUnhandledError(compile), + initialize: wrapUnhandledError(initialize), + initialized: wrapUnhandledError(initialized), + workspaceFoldersChanged: wrapUnhandledError(workspaceFoldersChanged), + watchedFilesChanged: wrapUnhandledError(watchedFilesChanged), + formatDocument: wrapUnhandledError(formatDocument), + gotoDefinition: wrapUnhandledError(gotoDefinition), + documentClosed: wrapUnhandledError(documentClosed), + documentOpened: wrapUnhandledError(documentOpened), + complete: wrapUnhandledError(complete), + findReferences: wrapUnhandledError(findReferences), + findDocumentHighlight: wrapUnhandledError(findDocumentHighlight), + prepareRename: wrapUnhandledError(prepareRename), + rename: wrapUnhandledError(rename), + renameFiles: wrapUnhandledError(renameFiles), + getSemanticTokens: wrapUnhandledError(getSemanticTokensForDocument), + buildSemanticTokens: wrapUnhandledError(buildSemanticTokens), + checkChange: wrapUnhandledError(checkChange), + getFoldingRanges: wrapUnhandledError(getFoldingRanges), + getHover: wrapUnhandledError(getHover), + getSignatureHelp: wrapUnhandledError(getSignatureHelp), + getDocumentSymbols: wrapUnhandledError(getDocumentSymbols), + getCodeActions: wrapUnhandledError(getCodeActions), + resolveCodeAction: wrapUnhandledError(resolveCodeAction), log, - reportDiagnostics: wrapUnhandledError("reportDiagnostics", reportDiagnostics), - - getInitProjectContext: wrapUnhandledError("getInitProjectContext", getInitProjectContext), - validateInitProjectTemplate: wrapUnhandledError( - "validateInitProjectTemplate", - validateInitProjectTemplate, - ), - initProject: wrapUnhandledError("initProject", initProject), - internalCompile: wrapUnhandledError("internalCompile", internalCompile), + reportDiagnostics: wrapUnhandledError(reportDiagnostics), + + getInitProjectContext: wrapUnhandledError(getInitProjectContext), + validateInitProjectTemplate: wrapUnhandledError(validateInitProjectTemplate), + initProject: wrapUnhandledError(initProject), + internalCompile: wrapUnhandledError(internalCompile), }; async function initialize(params: InitializeParams): Promise {