From f4dc20289adfa627ef0160c191f88706fec96995 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Thu, 28 May 2026 13:56:05 +0100 Subject: [PATCH] fix(devcontainer): fail-fast on Windows-native with HOME-not-set diagnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit's "Windows-native works" softening was wrong. VS Code on Windows-native resolves `${localEnv:HOME}` by reading the host shell's HOME env var, and cmd.exe has no HOME set — the bind sources collapse to `/.claude`, `/.codex`, etc., and Docker errors: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /.claude The @devcontainers/cli output that prompted the softening was misleading because I ran it from a Bash session with HOME already set, not from VS Code's cmd.exe call context. The original Finding-1 P0 — that Windows- native silently breaks the bind-mount feature — was correct. Three changes: 1. `ensure-host-config-dirs.cjs` detects the failure mode early: `if (process.platform === 'win32' && !process.env.HOME)` prints a targeted error message naming the root cause (cmd.exe has no HOME → ${localEnv:HOME} resolves empty → bind sources fail) and a step-by-step pointer to set up WSL2. Exits 1 so VS Code surfaces it as a clean container-creation failure, not the cryptic Docker bind-mount error. 2. README header reverted to "Windows 11 via WSL2" only (not "and Windows-native"). The "Windows 11 — WSL2 is required" section names the specific HOME-resolution mismatch concretely so future readers understand why the constraint exists. 3. Troubleshooting table gets a new row for the `ERROR: GitNexus devcontainer requires WSL2` message pointing at the setup section. --- .devcontainer/README.md | 13 ++++---- .devcontainer/ensure-host-config-dirs.cjs | 38 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index fd8001b53..2407fe629 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -1,6 +1,6 @@ # GitNexus Devcontainer -A cross-platform Dev Container that pre-installs Claude Code, OpenAI Codex CLI, and Cursor CLI alongside the GitNexus native build chain. Supported hosts: **macOS, Linux, Windows 11 via WSL2 (strongly recommended), and Windows 11 native (works but slower, with more bind-mount edge cases — see below).** +A cross-platform Dev Container that pre-installs Claude Code, OpenAI Codex CLI, and Cursor CLI alongside the GitNexus native build chain. Supported hosts: **macOS, Linux, and Windows 11 via WSL2** (Windows-native is unsupported — the bind mounts require `$HOME` to be set on the host shell, which cmd.exe on Windows-native lacks). ## Quick start @@ -11,13 +11,11 @@ A cross-platform Dev Container that pre-installs Claude Code, OpenAI Codex CLI, 5. Wait for the first build (~3–6 minutes) and `postCreateCommand` to finish installing workspace dependencies. 6. Authenticate the three CLIs once — see [First-time CLI authentication](#first-time-cli-authentication) below. -## Windows 11 — WSL2 is strongly recommended +## Windows 11 — WSL2 is required -Both WSL2 and Windows-native checkouts work. WSL2 is meaningfully better: +**Windows-native is unsupported.** The host bind mounts use `${localEnv:HOME}/.claude` (and `.codex`, `.cursor`, `.ssh`, `.config/git`, `.config/gh`, `.gitconfig`). VS Code resolves `${localEnv:HOME}` by reading the host shell's `HOME` env var — and cmd.exe on Windows-native has no `HOME` set. The bind sources then collapse to filesystem-root paths (`/.claude`, `/.codex`, …) and Docker rejects them with `bind source path does not exist`. The `initializeCommand`'s Node script detects this case and fails fast with a pointer at this section instead of letting Docker error opaquely. -- **File-watcher reliability.** Vite/jest `--watch` see file changes reliably on the WSL2 filesystem; Windows-native bind-mounts miss events intermittently. -- **`npm install` performance.** WSL2-side IO is ~3-5× faster for the heavy `gitnexus` install (tree-sitter native bindings, onnxruntime, vendored grammars). -- **No bind-mount permission edge cases.** Docker Desktop's Windows bind-mount permission translation can EPERM when a previous container created a file (e.g., `.husky/_/h`) and a new container with a different effective UID tries to overwrite it. WSL2-side filesystems don't have this class of issue (the `post-create.sh` defensive `rm -rf .husky/_` mitigates the most common case but isn't a full fix). +Beyond the `HOME` resolution issue, WSL2 also gives you reliable file watchers (Vite/jest `--watch` work), 3-5× faster `npm install` IO, and avoids the Docker Desktop Windows bind-mount permission edge cases (e.g., the husky `.husky/_/h` EPERM class). To clone and open the repo inside WSL2: @@ -189,7 +187,8 @@ Bump `CLAUDE_CODE_VERSION` and `CODEX_VERSION` in `.devcontainer/devcontainer.js | Symptom | Likely cause | Fix | |---------|--------------|-----| -| `EACCES` / `EPERM` writing into `~/.claude`, `~/.codex`, or `~/.cursor` inside the container | Windows-side bind-mount permission translation got out of sync after a UID change between rebuilds | Move the affected dir aside and let the CLI rebuild it (`mv ~/.claude ~/.claude.bak` and log in again). Long-term: clone in WSL2 — that filesystem doesn't hit this class of issue. See [Windows 11 — WSL2 is strongly recommended](#windows-11--wsl2-is-strongly-recommended) | +| `ERROR: GitNexus devcontainer requires WSL2 on Windows 11` from `initializeCommand` | You opened the repo from a Windows-native path; `cmd.exe` has no `$HOME`, so the bind mounts can't resolve | Clone the repo inside WSL2 and reopen — see [Windows 11 — WSL2 is required](#windows-11--wsl2-is-required) | +| `EACCES` / `EPERM` writing into `~/.claude`, `~/.codex`, or `~/.cursor` inside the container | Stale state from a previous container with a different effective UID | Move the affected dir aside and let the CLI rebuild it (`mv ~/.claude ~/.claude.bak` and log in again). Long-term: WSL2 setup, which doesn't hit this class of issue | | `EPERM: operation not permitted, copyfile ... '.husky/_/h'` in `postCreateCommand` | Leftover `.husky/_/` from a previous container run on a Windows-side bind mount | `post-create.sh` already runs `rm -rf .husky/_` defensively. If you hit this on an older config, delete `.husky/_/` on the host and rebuild. Long-term: clone in WSL2 | | Vite never hot-reloads | Repo cloned on Windows side, not WSL2 | Re-clone inside WSL2 | | `gitnexus-web` can't reach the backend | `4747` was remapped or backend isn't running | Verify the Ports panel shows `4747` forwarded with no remap; start the backend with `cd gitnexus && npx gitnexus serve` | diff --git a/.devcontainer/ensure-host-config-dirs.cjs b/.devcontainer/ensure-host-config-dirs.cjs index 7e6e95b57..a5c8281eb 100644 --- a/.devcontainer/ensure-host-config-dirs.cjs +++ b/.devcontainer/ensure-host-config-dirs.cjs @@ -16,6 +16,41 @@ const fs = require("fs"); const os = require("os"); const path = require("path"); +// Fail-fast on Windows-native (no $HOME set). VS Code resolves the bind +// mount sources via `${localEnv:HOME}` reading the host shell env, and +// cmd.exe on Windows has no HOME variable — so bind sources collapse to +// `/.claude`, `/.codex`, etc., and Docker rejects them with +// `bind source path does not exist`. Surface the actual root cause here +// instead of letting Docker error opaquely later. +if (process.platform === "win32" && !process.env.HOME) { + console.error("ERROR: GitNexus devcontainer requires WSL2 on Windows 11."); + console.error(""); + console.error( + "You opened this from a Windows-native path. The host bind mounts use", + ); + console.error( + "`${localEnv:HOME}/.claude` etc., which VS Code resolves from the host shell", + ); + console.error( + "env. cmd.exe on Windows has no HOME variable, so the bind sources resolve", + ); + console.error( + "to filesystem-root paths (/.claude, /.codex, ...) and Docker rejects them.", + ); + console.error(""); + console.error("Clone the repo inside WSL2 and re-open from there:"); + console.error(" wsl"); + console.error( + " cd ~ && git clone https://github.com/abhigyanpatwari/GitNexus.git", + ); + console.error(" cd GitNexus && code ."); + console.error(""); + console.error( + "See .devcontainer/README.md - Windows 11 - WSL2 is required.", + ); + process.exit(1); +} + const home = os.homedir(); for (const dir of [ @@ -26,6 +61,9 @@ for (const dir of [ path.join(".config", "gh"), path.join(".config", "git"), ]) { + if (fs.existsSync(path.join(home, dir))) { + continue; + } fs.mkdirSync(path.join(home, dir), { recursive: true }); }