Visual Studio Code Integration Guide

Technology: vscode · Category: tooling · Last reviewed: 2026-08-23

Source: https://tech-stack.codeamanilabs.org/guide/vscode

Insight:

VS Code's real superpower on Windows is not the editor — it's the client/server split. Install VS Code on Windows, type code . inside Ubuntu, and the UI stays on Windows while the language servers, terminal, debugger, and file watchers all run as Linux processes on the Linux filesystem. That is the only setup where a Next.js/TypeScript stack behaves identically on a dev laptop and on Vercel's Linux builders. The trade-off: extensions now live in two places and you have to know which side each one runs on.

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

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.

flowchart LR
  subgraph WIN["Windows 11 — client side"]
    A["VS Code workbench<br/>UI · themes · keybindings"]
    B["code CLI on PATH"]
  end
  subgraph WSL["WSL 2 · Ubuntu — server side"]
    C["VS Code Server<br/>~/.vscode-server"]
    D["Extension host<br/>ESLint · TS server · Tailwind"]
    E["Integrated terminal<br/>bash · node · git · claude"]
    F["Debugger + file watchers<br/>on /home/&lt;user&gt;/projects"]
  end
  B -->|"code . · code --remote wsl+Ubuntu"| A
  A <-->|"RPC over the WSL boundary"| C
  C --> D
  C --> E
  C --> F

Not to be confused with Visual Studio

VS Code (this guide) Visual Studio (see visual-studio/)
What it is Cross-platform editor, ~200 MB Full Windows IDE, multi-GB
Platforms Windows, macOS, Linux, web Windows (and a separate macOS product, retired)
Primary stack JS/TS, Python, Go, Rust, anything .NET, C++, MSBuild
Extension model Node/TypeScript extensions from the VS Code Marketplace VSIX / NuGet, Microsoft.VisualStudio.Extensibility or VSSDK + MEF
Remote dev First-class (WSL, SSH, containers, tunnels) Not the same model
Config JSON (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

Concern VS Code on Windows only VS Code + WSL extension
Node / package manager Windows build Linux build — same as CI and Vercel
Path & case semantics Case-insensitive, \ separators Case-sensitive, / — matches production
File watching Native Native on ext4; polling needed on WSL 1
node_modules native binaries Windows-compiled Linux-compiled
Terminal PowerShell / Git Bash Real bash in the distro
Docker / dev containers Docker Desktop Docker Desktop WSL 2 backend, or Docker in the distro

Official Documentation

Resource URL
Docs home https://code.visualstudio.com/docs
Developing in WSL https://code.visualstudio.com/docs/remote/wsl
WSL tutorial (start here) https://code.visualstudio.com/docs/remote/wsl-tutorial
Remote development overview https://code.visualstudio.com/docs/remote/remote-overview
Settings (scopes & precedence) https://code.visualstudio.com/docs/configure/settings
Debug configuration (launch.json) https://code.visualstudio.com/docs/debugtest/debugging-configuration
Tasks (tasks.json) https://code.visualstudio.com/docs/debugtest/tasks
Terminal profiles https://code.visualstudio.com/docs/terminal/profiles
Extension Marketplace https://code.visualstudio.com/docs/configure/extensions/extension-marketplace
code CLI reference https://code.visualstudio.com/docs/configure/command-line
Dev Containers https://code.visualstudio.com/docs/devcontainers/containers
WSL extension (Marketplace) https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl
Working across filesystems (Microsoft) https://learn.microsoft.com/en-us/windows/wsl/filesystems

Setup — Windows + WSL Ubuntu in five minutes

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

# 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:

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:

# 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 (F1) on the Windows side:

Command Does
WSL: Connect to WSL New window on the default distro
WSL: Connect to WSL using Distro Pick a specific distro
WSL: Reopen Folder in WSL Move the current folder to the Linux side
WSL: Reopen in Windows Move 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."

✅  /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:

{
  "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:

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:

// 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.

Scope Where Use it for
User %APPDATA%\Code\User\settings.json (Windows client) Theme, font, keybindings — travels everywhere
Remote Preferences: Open Remote Settings → the Remote tab Anything true only inside WSL (interpreter paths, watcher tuning)
Workspace .vscode/settings.json — commit this Team formatting, TS SDK, per-repo behavior
Workspace folder Per-folder in a multi-root .code-workspace Monorepo package overrides

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

{
  "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, Ctrl+`) 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:

{
  "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.

{
  "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:

{
  "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 F5.

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.

{
  "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 extension Claude Code CLI
Install anthropic.claude-code from the Marketplace Standalone install, inside the distro
Surface Native chat panel with inline diffs, plan review, @-mentions claude in the integrated terminal
Requires VS Code 1.94.0+ Nothing but a shell
PATH Bundles a private CLI copy — does not put claude on PATH Is the thing on your PATH
# In the VS Code integrated terminal (already a WSL bash shell)
claude

codeAmani notes


Troubleshooting

Issue Fix
code . not found in Ubuntu VS 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 lags Project is on /mnt/c. Move it to /home/<user>/… — Microsoft recommends against working across filesystems
Extension missing / greyed out It's installed Local but needs to run remote — click Install in WSL: Ubuntu in the Extensions view
Extension still runs on the wrong side Force it with "remote.extensionKind": { "<publisher.ext>": ["ui" | "workspace"] } — sparingly, it can break extensions
EACCES: permission denied renaming a folder Known 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 Git CRLF/LF mismatch — set files.eol: "\n" and core.autocrlf input
Env var visible in bash but not to a task/debug session The server skips shell startup scripts — put it in ~/.vscode-server/server-env-setup
Debug session never starts after a watch task isBackground task missing a background problem matcher (beginsPattern/endsPattern)
Extensions fail on Alpine distros glibc dependencies in native extension code — use Ubuntu/Debian for the dev distro
Git pull/sync hangs on a remote window Passphrase-protected SSH key — clone over HTTPS or push from the terminal
VS Code can't see ANTHROPIC_API_KEY Launch it from the shell with code . so it inherits the environment

Official docs: