-
Notifications
You must be signed in to change notification settings - Fork 4.1k
fix: handle non-file URI schemes in runTerminalCommand for WSL2 #9325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
788e253
eab2ac1
074a6c7
3ef9f89
27adb1d
e251113
6b30a88
8e5a7c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # Continue Development Notes | ||
|
|
||
| ## WSL2 Development | ||
|
|
||
| ### Building VSIX from WSL2 | ||
|
|
||
| The VS Code extension VSIX built on Linux contains Linux-only native binaries (sqlite3, LanceDB, ripgrep). When VS Code runs in WSL remote mode, extensions can run in either: | ||
|
|
||
| - **Windows extension host** - needs Windows binaries | ||
| - **WSL extension host** - Linux binaries work | ||
|
|
||
| If both hosts try to load the extension, the Windows host fails with: | ||
|
|
||
| ``` | ||
| Error: node_sqlite3.node is not a valid Win32 application | ||
| ``` | ||
|
|
||
| **Workaround for local testing:** | ||
|
|
||
| 1. Build VSIX normally: `npm run package` (in extensions/vscode) | ||
| 2. Install: `code --install-extension extensions/vscode/build/continue-*.vsix` | ||
| 3. Delete Windows installation to force WSL-only: | ||
| ```bash | ||
| rm -rf /mnt/c/Users/<username>/.vscode/extensions/continue.continue-* | ||
| ``` | ||
| 4. Reload VS Code | ||
|
|
||
| This forces Continue to run exclusively in WSL extension host where Linux binaries work. | ||
|
|
||
| **Related:** GitHub Issue #9326 | ||
|
|
||
| ### File Count and Extension Activation | ||
|
|
||
| Large file counts (300K+) from node_modules and build artifacts can cause extension activation issues. If Continue fails to load: | ||
|
|
||
| 1. Delete build artifacts: | ||
| ```bash | ||
| rm -rf */node_modules */*/node_modules node_modules | ||
| rm -rf */dist */*/dist */out */*/out */build | ||
| ``` | ||
| 2. Reload VS Code | ||
| 3. Rebuild only when needed for testing | ||
|
|
||
| ### Terminal Command Working Directory | ||
|
|
||
| The `run_terminal_command` tool resolves workspace directories from VS Code URIs. In WSL2: | ||
|
|
||
| - URIs are `vscode-remote://wsl+Ubuntu/path` (not `file://`) | ||
| - Must parse with `new URL()` and extract pathname | ||
| - Must `decodeURIComponent()` for paths with spaces/special chars | ||
|
|
||
| See: `core/tools/implementations/runTerminalCommand.ts` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { fileURLToPath } from "node:url"; | ||
|
|
||
| /** | ||
| * Test suite for workspace directory resolution logic. | ||
| * | ||
| * This tests the URI parsing behavior used in runTerminalCommand.ts | ||
| * to ensure correct handling of various workspace URI formats. | ||
| */ | ||
|
|
||
| // Replicate the resolution logic for testing | ||
| function resolveWorkingDirectory(workspaceDirs: string[]): string { | ||
| // Handle vscode-remote://wsl+distro/path URIs (WSL2 remote workspaces) | ||
| const wslWorkspaceDir = workspaceDirs.find((dir) => | ||
| dir.startsWith("vscode-remote://wsl"), | ||
| ); | ||
| if (wslWorkspaceDir) { | ||
| try { | ||
| const url = new URL(wslWorkspaceDir); | ||
| return decodeURIComponent(url.pathname); | ||
| } catch { | ||
| // Fall through to other handlers | ||
| } | ||
| } | ||
|
|
||
| // Handle file:// URIs (local workspaces) | ||
| const fileWorkspaceDir = workspaceDirs.find((dir) => | ||
| dir.startsWith("file:/"), | ||
| ); | ||
| if (fileWorkspaceDir) { | ||
| try { | ||
| return fileURLToPath(fileWorkspaceDir); | ||
| } catch { | ||
| // Fall through to default handling | ||
| } | ||
| } | ||
|
|
||
| // Default to user's home directory with fallbacks | ||
| try { | ||
| return process.env.HOME || process.env.USERPROFILE || process.cwd(); | ||
| } catch { | ||
| return "/tmp"; | ||
| } | ||
| } | ||
|
|
||
| describe("resolveWorkingDirectory", () => { | ||
| describe("WSL remote URIs (vscode-remote://wsl+...)", () => { | ||
| it("should parse basic WSL URI", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu/home/user/project", | ||
| ]); | ||
| expect(result).toBe("/home/user/project"); | ||
| }); | ||
|
|
||
| it("should decode URL-encoded spaces in path", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu/home/user/my%20project", | ||
| ]); | ||
| expect(result).toBe("/home/user/my project"); | ||
| }); | ||
|
|
||
| it("should decode URL-encoded special characters", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu/home/user/path%23with%23hashes", | ||
| ]); | ||
| expect(result).toBe("/home/user/path#with#hashes"); | ||
| }); | ||
|
|
||
| it("should decode URL-encoded unicode characters", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu/home/user/%E4%B8%AD%E6%96%87%E8%B7%AF%E5%BE%84", | ||
| ]); | ||
| expect(result).toBe("/home/user/中文路径"); | ||
| }); | ||
|
|
||
| it("should handle different WSL distro names", () => { | ||
| const ubuntu = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu-22.04/home/user/project", | ||
| ]); | ||
| expect(ubuntu).toBe("/home/user/project"); | ||
|
|
||
| const debian = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Debian/home/user/project", | ||
| ]); | ||
| expect(debian).toBe("/home/user/project"); | ||
| }); | ||
|
|
||
| it("should handle root path", () => { | ||
| const result = resolveWorkingDirectory(["vscode-remote://wsl+Ubuntu/"]); | ||
| expect(result).toBe("/"); | ||
| }); | ||
|
|
||
| it("should prioritize WSL URIs over file:// URIs", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "file:///c:/Users/user/project", | ||
| "vscode-remote://wsl+Ubuntu/home/user/project", | ||
| ]); | ||
| expect(result).toBe("/home/user/project"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("file:// URIs (local workspaces)", () => { | ||
| it("should parse basic file:// URI on Unix", () => { | ||
| const result = resolveWorkingDirectory(["file:///home/user/project"]); | ||
| expect(result).toBe("/home/user/project"); | ||
| }); | ||
|
|
||
| it("should decode URL-encoded spaces in file:// URI", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "file:///home/user/my%20project", | ||
| ]); | ||
| expect(result).toBe("/home/user/my project"); | ||
| }); | ||
|
|
||
| it("should handle Windows-style file:// URI", () => { | ||
| // fileURLToPath handles Windows paths correctly | ||
| const result = resolveWorkingDirectory(["file:///C:/Users/user/project"]); | ||
| // On Unix, this will be /C:/Users/user/project | ||
| // On Windows, this will be C:\Users\user\project | ||
| expect(result).toMatch(/project$/); | ||
| }); | ||
| }); | ||
|
|
||
| describe("fallback behavior", () => { | ||
| it("should fall back to HOME when no valid URIs", () => { | ||
| const originalHome = process.env.HOME; | ||
| try { | ||
| process.env.HOME = "/test/home"; | ||
| const result = resolveWorkingDirectory([]); | ||
| expect(result).toBe("/test/home"); | ||
| } finally { | ||
| process.env.HOME = originalHome; | ||
| } | ||
| }); | ||
|
|
||
| it("should handle empty workspace dirs array", () => { | ||
| const result = resolveWorkingDirectory([]); | ||
| // Should return HOME or USERPROFILE or cwd | ||
| expect(typeof result).toBe("string"); | ||
| expect(result.length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it("should handle invalid URIs gracefully", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "not-a-valid-uri", | ||
| "also://not/handled", | ||
| ]); | ||
| // Should fall through to HOME fallback | ||
| expect(typeof result).toBe("string"); | ||
| }); | ||
|
|
||
| it("should handle malformed vscode-remote URI", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu", // Missing path | ||
| ]); | ||
| // new URL() should still parse this, pathname would be empty or "/" | ||
| expect(typeof result).toBe("string"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("URL encoding edge cases", () => { | ||
| it("should handle plus signs (not spaces)", () => { | ||
| // In URL encoding, + is literal plus, %2B is encoded plus, %20 is space | ||
| const result = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu/home/user/c%2B%2B-project", | ||
| ]); | ||
| expect(result).toBe("/home/user/c++-project"); | ||
| }); | ||
|
|
||
| it("should handle percent sign itself", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu/home/user/100%25-complete", | ||
| ]); | ||
| expect(result).toBe("/home/user/100%-complete"); | ||
| }); | ||
|
|
||
| it("should handle mixed encoded and unencoded characters", () => { | ||
| const result = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu/home/user/normal-path/with%20space/more", | ||
| ]); | ||
| expect(result).toBe("/home/user/normal-path/with space/more"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("comparison with fileURLToPath behavior", () => { | ||
| it("should match fileURLToPath decoding for equivalent paths", () => { | ||
| const fileResult = fileURLToPath("file:///home/user/my%20project"); | ||
| const wslResult = resolveWorkingDirectory([ | ||
| "vscode-remote://wsl+Ubuntu/home/user/my%20project", | ||
| ]); | ||
|
|
||
| // Both should decode %20 to space | ||
| expect(fileResult).toBe("/home/user/my project"); | ||
| expect(wslResult).toBe("/home/user/my project"); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,12 +48,32 @@ import { getBooleanArg, getStringArg } from "../parseArgs"; | |
| * Falls back to home directory or temp directory if no workspace is available. | ||
| */ | ||
| function resolveWorkingDirectory(workspaceDirs: string[]): string { | ||
| // Handle vscode-remote://wsl+distro/path URIs (WSL2 remote workspaces) | ||
| const wslWorkspaceDir = workspaceDirs.find((dir) => | ||
| dir.startsWith("vscode-remote://wsl"), | ||
|
||
| ); | ||
| if (wslWorkspaceDir) { | ||
| try { | ||
| const url = new URL(wslWorkspaceDir); | ||
| return decodeURIComponent(url.pathname); // e.g., "/home/user/project" | ||
| } catch { | ||
| // Fall through to other handlers | ||
| } | ||
| } | ||
|
|
||
| // Handle file:// URIs (local workspaces) | ||
| const fileWorkspaceDir = workspaceDirs.find((dir) => | ||
| dir.startsWith("file:/"), | ||
| ); | ||
| if (fileWorkspaceDir) { | ||
| return fileURLToPath(fileWorkspaceDir); | ||
| try { | ||
| return fileURLToPath(fileWorkspaceDir); | ||
| } catch { | ||
| // fileURLToPath can fail on malformed URIs or in some remote environments | ||
| // Fall through to default handling | ||
| } | ||
| } | ||
|
|
||
| // Default to user's home directory with fallbacks | ||
| try { | ||
| return process.env.HOME || process.env.USERPROFILE || process.cwd(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use AGENTS.md instead of CLAUDE.md, if any updates are need to AGENTS.md could you make them there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, that was an oversight to be included at all. Removed.