← Back to dashboard

Visual Studio Code Integration Guide

What is VS Code + WSL?

The real model

The setup that makes a Windows laptop behave like the Linux box you actually deploy to.

Keep repos at /home/<user>/projects — editing /mnt/c goes over the 9p bridge and npm install, Turbopack HMR, and file watching all degrade; Microsoft explicitly recommends against working across filesystems. The server does NOT run shell startup scripts, so an env var you can echo in bash can be invisible to a task or debug session — that belongs in ~/.vscode-server/server-env-setup. An isBackground task without a background problem matcher (activeOnStart/beginsPattern/endsPattern) makes any preLaunchTask hang forever with no error. Set files.eol to \n plus core.autocrlf=input or a repo touched from both sides shows every file as modified. For codeAmani this is the standard Windows setup: every codeAmani-labs-projects app is a Linux-built Next.js 15 app shipping to Vercel's Linux runners, and Claude Code lives here too — the anthropic.claude-code extension needs VS Code 1.94+, bundles a private CLI for its panel, and deliberately does not add `claude` to PATH, so the CLI must be installed inside the distro to run in the integrated terminal.

Six things you configure

One remote window, one committed .vscode/ folder — the rest of the team gets your setup for free.

Text
 ██╗   ██╗███████╗     ██████╗ ██████╗ ██████╗ ███████╗
 ██║   ██║██╔════╝    ██╔════╝██╔═══██╗██╔══██╗██╔════╝
 ██║   ██║███████╗    ██║     ██║   ██║██║  ██║█████╗
 ╚██╗ ██╔╝╚════██║    ██║     ██║   ██║██║  ██║██╔══╝
  ╚████╔╝ ███████║    ╚██████╗╚██████╔╝██████╔╝███████╗
   ╚═══╝  ╚══════╝     ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝

Visual Studio Code Integration Guide

Focus: A developer's guide to VS Code driving WSL Ubuntu from Windows — the WSL extension, code ., extension placement, workspace/remote settings, the integrated terminal, tasks, debugging, dev containers, and running Claude Code inside it.

Overview

VS Code is a free, cross-platform editor built on Electron with a remote-capable architecture: the workbench (UI, themes, keybindings) runs on your machine, while a VS Code Server process can run somewhere else — inside a WSL distro, over SSH, or in a container. The WSL extension (ms-vscode-remote.remote-wsl) installs that server into your Linux distro and runs "commands and other extensions directly in WSL so you can edit files located in WSL or the mounted Windows filesystem (for example /mnt/c) without worrying about pathing issues, binary compatibility, or other cross-OS challenges."

For codeAmani that matters because the whole stack — Next.js 15, Node, pnpm/npm, Prisma engines, sharp, Playwright browsers, Docker — is built and tested on Linux. Developing on Windows-native Node and deploying to Vercel's Linux builders is the classic source of "works on my machine": case-sensitive imports, node_modules binaries compiled for the wrong platform, CRLF diffs, and path separators. Remote-WSL removes the class entirely.

Not to be confused with Visual Studio

VS Code (this guide)Visual Studio (see visual-studio/)
What it isCross-platform editor, ~200 MBFull Windows IDE, multi-GB
PlatformsWindows, macOS, Linux, webWindows (and a separate macOS product, retired)
Primary stackJS/TS, Python, Go, Rust, anything.NET, C++, MSBuild
Extension modelNode/TypeScript extensions from the VS Code MarketplaceVSIX / NuGet, Microsoft.VisualStudio.Extensibility or VSSDK + MEF
Remote devFirst-class (WSL, SSH, containers, tunnels)Not the same model
ConfigJSON (settings.json, launch.json, tasks.json).sln / .csproj / MSBuild

They share a name and nothing else. This guide is VS Code.

Local Windows vs Remote-WSL

ConcernVS Code on Windows onlyVS Code + WSL extension
Node / package managerWindows buildLinux build — same as CI and Vercel
Path & case semanticsCase-insensitive, \ separatorsCase-sensitive, / — matches production
File watchingNativeNative on ext4; polling needed on WSL 1
node_modules native binariesWindows-compiledLinux-compiled
TerminalPowerShell / Git BashReal bash in the distro
Docker / dev containersDocker DesktopDocker Desktop WSL 2 backend, or Docker in the distro

Official Documentation


Setup — Windows + WSL Ubuntu in five minutes

Order matters: WSL first, VS Code on the Windows side, extension last.

Bash
# 1. Windows side — install WSL 2 + Ubuntu (PowerShell as Administrator, once)
wsl --install -d Ubuntu

# 2. Install VS Code on WINDOWS, not inside the distro.
#    https://code.visualstudio.com/download
#    On the "Select Additional Tasks" screen, CHECK "Add to PATH".

# 3. Install the WSL extension (any shell where `code` is on PATH)
code --install-extension ms-vscode-remote.remote-wsl

# ...or the whole Remote Development pack (WSL + SSH + Dev Containers)
code --install-extension ms-vscode-remote.vscode-remote-extensionpack

# 4. Verify
code --version
code --list-extensions --show-versions

Do not apt install code inside Ubuntu. The WSL extension pushes its own VS Code Server into ~/.vscode-server; a second Linux-native VS Code is redundant and confuses code on the distro PATH.

The daily loop

From an Ubuntu shell, in the project folder:

Bash
cd ~/projects/boda-dispatch
code .

First run downloads the server components into WSL (once, ~30s). A WSL: Ubuntu indicator appears in the bottom-left status bar — that is the single reliable signal that you are editing on the Linux side.

Other entry points:

Bash
# From Windows PowerShell / CMD — open a WSL path directly
code --remote wsl+Ubuntu /home/<user>/projects/boda-dispatch

# Force folder interpretation for a path containing a dot
code --folder-uri vscode-remote://wsl+Ubuntu/home/<user>/app.v2

# Already inside a WSL window? The same code CLI works there too
code --diff old.ts new.ts
code --goto lib/domain/dispatch.ts:42

From the Command Palette (<kbd>F1</kbd>) on the Windows side:

CommandDoes
WSL: Connect to WSLNew window on the default distro
WSL: Connect to WSL using DistroPick a specific distro
WSL: Reopen Folder in WSLMove the current folder to the Linux side
WSL: Reopen in WindowsMove it back

Put the code on the Linux filesystem

Microsoft is unambiguous: "We recommend against working across operating systems with your files… For the fastest performance speed, store your files in the WSL file system if you are working in a Linux command line."

Text
✅  /home/<user>/projects/boda-dispatch      ← ext4, fast, correct case semantics
❌  /mnt/c/Users/<user>/projects/boda-...    ← 9p bridge; npm install and HMR crawl

Editing /mnt/c works and VS Code handles the paths, but npm install, Turbopack HMR, and file watching over the Windows mount are dramatically slower. To browse the Linux files from Windows Explorer, run explorer.exe . from the WSL shell, or type \\wsl$ in the Explorer address bar.


Extensions: two installs, two homes

Once connected, the Extensions view splits into Local - Installed (UI-side: themes, icons, keymaps) and WSL: Ubuntu - Installed (everything that touches code or the filesystem: language servers, linters, formatters, debuggers, test runners). Installing from the Extensions view while connected puts the extension in the right place automatically. Extensions that should be remote but are only installed locally appear dimmed with an Install in WSL: Ubuntu button, and the cloud icon in the Local - Installed title bar offers Install Local Extensions in WSL: {Name} for a bulk move.

Commit the stack's recommendations so a new machine is one click from correct — .vscode/extensions.json:

JSON
{
  "recommendations": [
    "ms-vscode-remote.remote-wsl",
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "bradlc.vscode-tailwindcss",
    "Prisma.prisma",
    "vitest.explorer",
    "ms-playwright.playwright",
    "eamodio.gitlens",
    "ms-vscode-remote.remote-containers",
    "anthropic.claude-code"
  ]
}

VS Code surfaces these as Workspace Recommendations; Extensions: Configure Recommended Extensions (Workspace Folder) generates the file. Scripted setup inside the distro:

Bash
code --install-extension dbaeumer.vscode-eslint \
     --install-extension esbenp.prettier-vscode \
     --install-extension bradlc.vscode-tailwindcss --force

Rarely, an extension guesses wrong about where to run. Override it explicitly:

JSON
// settings.json — "ui" = local/client, "workspace" = remote (WSL)
"remote.extensionKind": {
  "ms-azuretools.vscode-containers": ["ui"]
}

Use this sparingly — the docs warn it can break extensions.


Settings: four scopes, one precedence order

Precedence, lowest → highest: default → user → remote → workspace → workspace folder → language-specific → policy. The remote layer is the one people forget.

ScopeWhereUse it for
User%APPDATA%\Code\User\settings.json (Windows client)Theme, font, keybindings — travels everywhere
RemotePreferences: Open Remote Settings → the Remote tabAnything true only inside WSL (interpreter paths, watcher tuning)
Workspace.vscode/settings.json — commit thisTeam formatting, TS SDK, per-repo behavior
Workspace folderPer-folder in a multi-root .code-workspaceMonorepo package overrides

A sane committed .vscode/settings.json for the Next.js/TypeScript stack:

JSON
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" },

  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true,
  "typescript.preferences.importModuleSpecifier": "non-relative",

  "files.eol": "\n",
  "files.exclude": { "**/.next": true },
  "files.watcherExclude": { "**/node_modules/**": true, "**/.next/**": true },
  "search.exclude": { "**/node_modules": true, "**/.next": true, "**/dist": true },

  "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]]
}

"files.eol": "\n" is not cosmetic here — the same repo touched from Windows and WSL is the documented cause of "every file is modified" Git noise.


Integrated terminal

Once a folder is open in WSL, any terminal you open (Terminal → New Terminal, <kbd>Ctrl</kbd>+<kbd>`</kbd>) is already a bash shell in the distro, cwd at the workspace root. No profile configuration needed — that is the whole point.

For local Windows windows, VS Code auto-detects WSL distros as terminal profiles (terminal.integrated.useWslProfiles, on by default). To pin one:

JSON
{
  "terminal.integrated.defaultProfile.windows": "Ubuntu (WSL)",
  "terminal.integrated.profiles.windows": {
    "Ubuntu (WSL)": { "path": "C:\\WINDOWS\\System32\\wsl.exe", "args": ["-d", "Ubuntu"] }
  }
}

Gotcha: when the VS Code Server starts in WSL, no shell startup scripts are run — .bashrc/.profile are skipped for the server process. Terminals you open still source them, but tasks and debug sessions inherit the server's environment. If a tool needs env setup before the server boots, put it in ~/.vscode-server/server-env-setup, which is processed before the server starts.

terminal.integrated.automationProfile.<platform> gives tasks and the debugger a lighter shell when your interactive profile has heavy startup (oh-my-zsh, nvm, direnv).


Tasks — tasks.json

Tasks run in WSL when the window is remote, so npm run dev is Linux npm. npm scripts are auto-detected (Tasks: Run Task); write explicit tasks when you need a preLaunchTask, a problem matcher, or ordering.

JSON
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "dev",
      "type": "shell",
      "command": "npm run dev",
      "isBackground": true,
      "problemMatcher": {
        "owner": "typescript",
        "pattern": { "regexp": "^$" },
        "background": {
          "activeOnStart": true,
          "beginsPattern": "starting the development server",
          "endsPattern": "Ready in"
        }
      },
      "presentation": { "reveal": "always", "panel": "dedicated" }
    },
    {
      "label": "typecheck",
      "type": "shell",
      "command": "npx tsc --noEmit",
      "group": { "kind": "build", "isDefault": true },
      "problemMatcher": ["$tsc"]
    },
    {
      "label": "verify",
      "dependsOn": ["typecheck", "lint", "test"],
      "dependsOrder": "sequence",
      "group": "test"
    }
  ]
}

isBackground: true requires a background matcher with beginsPattern/endsPattern — without it a watch task used as preLaunchTask hangs the debug launch forever. Set options.cwd for monorepo packages.


Debugging — launch.json

Every launch config needs type, request (launch | attach), and name. In a WSL window the app starts in WSL and the debugger attaches there; nothing extra to configure. Next.js's own documented configuration:

JSON
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Next.js: debug server-side",
      "type": "node-terminal",
      "request": "launch",
      "command": "npm run dev -- --inspect"
    },
    {
      "name": "Next.js: debug client-side",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3000"
    },
    {
      "name": "Next.js: debug full stack",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/next/dist/bin/next",
      "runtimeArgs": ["--inspect"],
      "skipFiles": ["<node_internals>/**"],
      "serverReadyAction": {
        "action": "debugWithChrome",
        "killOnServerStop": true,
        "pattern": "- Local:.+(https?://.+)",
        "uriFormat": "%s",
        "webRoot": "${workspaceFolder}"
      }
    }
  ]
}

Attributes worth knowing: env / envFile (point at .env.local), cwd (monorepos), console: "integratedTerminal", preLaunchTask / postDebugTask, compounds to start server + client together, and ${workspaceFolder} / ${env:NAME} substitution. serverReadyAction.pattern scans stdout and opens a browser debug session on the captured URL — that is what makes full-stack breakpoints work in one <kbd>F5</kbd>.

WSL port forwarding is automatic. A dev server bound in the distro is reachable at http://localhost:3000 from the Windows browser; VS Code's Ports view lists forwarded ports for remote windows.


Dev containers from WSL

With Docker Desktop's WSL 2 backend (Settings → Resources → WSL Integration, enable your distro), open the folder in WSL first, then run Dev Containers: Reopen in Container. If there is no .devcontainer/devcontainer.json, VS Code offers Dev Containers: Add Dev Container Configuration Files.

JSON
{
  "name": "codeAmani Next.js",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:22",
  "features": { "ghcr.io/devcontainers/features/github-cli:1": {} },
  "forwardPorts": [3000],
  "postCreateCommand": "npm ci",
  "remoteUser": "node",
  "customizations": {
    "vscode": {
      "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "bradlc.vscode-tailwindcss"],
      "settings": { "editor.formatOnSave": true }
    }
  }
}

Rule of thumb: WSL for daily work (fast, zero ceremony, one shared Node/pnpm store), dev containers when the environment itself is the deliverable — a pinned Postgres + app pair, or onboarding where "install these six things" is the bottleneck. customizations.vscode.extensions is the container's answer to .vscode/extensions.json.


Claude Code inside VS Code

Two distinct things, and mixing them up is the usual confusion:

Claude Code VS Code extensionClaude Code CLI
Installanthropic.claude-code from the MarketplaceStandalone install, inside the distro
SurfaceNative chat panel with inline diffs, plan review, @-mentionsclaude in the integrated terminal
RequiresVS Code 1.94.0+Nothing but a shell
PATHBundles a private CLI copy — does not put claude on PATHIs the thing on your PATH
Bash
# In the VS Code integrated terminal (already a WSL bash shell)
claude
  • Toggle focus between editor and Claude with <kbd>Ctrl</kbd>+<kbd>Esc</kbd> (<kbd>Cmd</kbd>+<kbd>Esc</kbd> on macOS).
  • The CLI auto-detects the IDE when launched from VS Code's terminal (diff viewing, diagnostics sharing). From an external terminal, run /ide inside Claude Code to connect it to VS Code.
  • Prefer the terminal UI in the panel? Enable the extension's Use Terminal setting (claudeCode.useTerminal).
  • WSL note: the integrated terminal in a remote window is a Linux shell, so the CLI must be installed inside the distro. A Windows-side claude.exe is not on that PATH.
  • Launching VS Code as code . from a shell means it inherits that shell's environment — the documented fix when the extension can't see ANTHROPIC_API_KEY.

codeAmani notes

  • Standard Windows setup: VS Code on Windows + WSL 2 Ubuntu + the WSL extension, project files under /home/<user>/projects/. Every codeAmani-labs-projects/* app is a Linux-built Next.js app deployed to Vercel/Netlify Linux runners; developing on Windows-native Node re-introduces case-sensitivity and native-binary bugs that CI will find later and more expensively.
  • Secrets stay out of committed JSON. .vscode/settings.json, launch.json, and tasks.json are committed — never put keys in env blocks. Use "envFile": "${workspaceFolder}/.env.local" (gitignored) or inject through Hazina. .env.local living on the Linux side also keeps it out of Windows-indexed folders and OneDrive sync.
  • .vscode/ is a team artifact. Commit settings.json, extensions.json, launch.json, tasks.json; gitignore .vscode/*.log and any machine-local scratch. A new laptop should reach a working state with wsl --install, code ., and "Install All" on the recommendations prompt.
  • Line endings. files.eol: "\n" plus git config --global core.autocrlf input inside the distro. Editing one repo from both Windows and WSL without this produces whole-file diffs — the single most common WSL Git complaint.
  • Git credentials. Configure WSL to use the Windows Git Credential Manager rather than duplicating tokens in the distro. Note the documented limitation: cloning over SSH with a passphrase-protected key can hang VS Code's pull/sync — use HTTPS, or a passphrase-less key, or push from the CLI.
  • Low-bandwidth (Kenya-targeted work): the remote server download is a one-time ~tens-of-MB hit per distro, and extensions are then installed into WSL — so a shared .vscode/extensions.json plus a scripted code --install-extension run beats each engineer discovering extensions ad hoc over a metered connection. Settings Sync carries settings/keybindings/extension lists to a new machine without re-downloading a profile by hand.
  • AI routing is unchanged by the editor. VS Code is where Claude Code runs; the provider policy in CLAUDE.md (Anthropic primary, OpenAI for structured output, HuggingFace/Together for open models) still governs application code.

Troubleshooting

IssueFix
code . not found in UbuntuVS Code wasn't installed with Add to PATH on Windows, or the terminal predates the install — restart the shell, or reinstall checking the box
Editing feels slow, HMR lagsProject is on /mnt/c. Move it to /home/<user>/… — Microsoft recommends against working across filesystems
Extension missing / greyed outIt's installed Local but needs to run remote — click Install in WSL: Ubuntu in the Extensions view
Extension still runs on the wrong sideForce it with "remote.extensionKind": { "<publisher.ext>": ["ui" | "workspace"] } — sparingly, it can break extensions
EACCES: permission denied renaming a folderKnown WSL 1 issue — set remote.WSL.fileWatcher.polling: true (and raise remote.WSL.fileWatcher.pollingInterval on large repos), or move to WSL 2
Whole repo shows as modified in GitCRLF/LF mismatch — set files.eol: "\n" and core.autocrlf input
Env var visible in bash but not to a task/debug sessionThe server skips shell startup scripts — put it in ~/.vscode-server/server-env-setup
Debug session never starts after a watch taskisBackground task missing a background problem matcher (beginsPattern/endsPattern)
Extensions fail on Alpine distrosglibc dependencies in native extension code — use Ubuntu/Debian for the dev distro
Git pull/sync hangs on a remote windowPassphrase-protected SSH key — clone over HTTPS or push from the terminal
VS Code can't see ANTHROPIC_API_KEYLaunch it from the shell with code . so it inherits the environment