Skip to content

typescript.native-preview.tsdk: relative path in committed .vscode/settings.json gets overwritten with absolute path when accepting the workspace version prompt #4070

Description

@Cellule

Extension Version

Latest (post #3516, merged Apr 23 2026) — e.g. 0.20260424.x or later

VS Code Version

1.100+

Operating system Version

macOS (but the absolute-path problem affects all platforms)

Steps to reproduce

  1. Commit a .vscode/settings.json that includes "typescript.native-preview.tsdk": "./node_modules/@typescript/native-preview" (relative path, so it works for the whole team regardless of machine)
  2. Open the workspace in VS Code with the TypeScript Native Preview extension installed
  3. See the prompt: "This workspace contains a TypeScript Native Preview version (x.y.z). Would you like to use the workspace version?"
  4. Click Allow
  5. Check git status.vscode/settings.json is now dirty

Issue

Expected: The extension recognises the pre-existing relative tsdk path and either skips the prompt or, if the user confirms, leaves the setting value unchanged (relative paths committed to source control must stay relative so they work on every developer's machine).

Actual: Clicking Allow overwrites the relative path with a machine-specific absolute path, e.g.:

"typescript.native-preview.tsdk": "/Users/alice/projects/myrepo/node_modules/@typescript/native-preview"

This dirtied file cannot be committed — it would break the setting for every other team member. The prompt also re-fires on every workspace open because the extension never marks it as "already opted in" when the tsdk setting is set via a relative path.

Contrast with the built-in extension: "js/ts.tsdk.path": "./node_modules/typescript/lib" has worked reliably with this committed-settings workflow for years and does not exhibit this problem.


Root cause analysis

⚠️ Disclaimer: the following analysis was assisted by AI (Claude Sonnet 4.5) and may contain inaccuracies. It is provided as a starting point for investigation, not as a definitive diagnosis.

Bug 1 — promptUseWorkspaceVersion writes an absolute path when the user clicks Allow

In _extension/src/session.ts, findWorkspaceNativePreviewPackages() constructs tsdkPath using vscode.Uri.joinPath(folder.uri, ...).fsPath, which is always an absolute filesystem path:

// _extension/src/session.ts – findWorkspaceNativePreviewPackages()
const packagePath = vscode.Uri.joinPath(folder.uri, "node_modules", "@typescript", "native-preview");
tsdkPath: path.normalize(packagePath.fsPath),  // ← always absolute

When the user clicks Allow, that absolute value is written back to the workspace setting:

// _extension/src/session.ts – promptUseWorkspaceVersion()
await config.update("tsdk", wsVersion.tsdkPath, vscode.ConfigurationTarget.Workspace);
// ↑ writes e.g. "/Users/alice/projects/myrepo/node_modules/@typescript/native-preview"

Bug 2 — The prompt fires even when tsdk is already set and resolves to the same package

The prompt checks only whether useWorkspaceTsdkStorageKey (in workspaceState) is true; it does not check whether the tsdk setting already resolves to a valid local install. So a developer who already has "./node_modules/@typescript/native-preview" in their committed settings still sees the prompt every time and risks dirtying the file.

How the built-in TypeScript extension avoids both problems

The built-in extension (extensions/typescript-language-features) uses two design choices:

  1. pathLabel separation: TypeScriptVersion stores both the resolved absolute path (used for execution) and the original _pathLabel (used for config writes). The version picker writes version.pathLabel, so a relative path stays relative.

  2. The prompt does not write to the setting: promptUseWorkspaceTsdk in versionManager.ts only calls workspaceState.update(useWorkspaceTsdkStorageKey, true) — it never calls config.update(). The in-memory active version is updated; the setting is left untouched.

// extensions/typescript-language-features/src/tsServer/versionManager.ts
if (result === allowIt) {
    await this.workspaceState.update(useWorkspaceTsdkStorageKey, true);
    this.updateActiveVersion(workspaceVersion); // ← no config.update()
}

Suggested fixes

Fix 1 — Don't write to the setting in promptUseWorkspaceVersion
Mirror the built-in extension: when the user clicks Allow, update workspaceState but do not call config.update(). The relative path already committed in .vscode/settings.json is sufficient.

Fix 2 — Treat a pre-existing tsdk that resolves to a local install as already opted-in
Before showing the prompt, check whether the current tsdk setting (after relative-path resolution) already points to the same package that was detected. If so, skip the prompt.

Fix 3 — When the version picker must write back, prefer a relative path
If the selected version lives inside a workspace folder, compute path.relative(folder.uri.fsPath, absoluteTsdkPath) before calling config.update(). This mirrors how pathLabel works in the built-in extension.

Metadata

Metadata

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions