Fix: Windows Quick Start Does Not Work
IMPLEMENTATION RULES: Before implementing this plan, read and follow:
- WORKFLOW.md - The implementation process
- PLANS.md - Plan structure and best practices
Status: Active​
Goal: A Windows user who follows the Quick Start on dct.sovereignsky.no/docs gets a running devcontainer.
Who this is for: ordinary Windows office users with no knowledge of git, containers or Docker (Terje, 2026-09-25). Every message this plan touches must be plain language with the next action spelled out. This plan fixes the defects in today's script; installing the prerequisites for the user (WSL, Rancher Desktop, VS Code) belongs to helpers-no/client-provisioning (decision 2026-09-25, see PLAN-host-installer-handover).
Priority: High — every new Windows install on a PC without Unix tools on PATH (the normal office PC) is affected. Reported by Terje, 2026-09-24.
Last Updated: 2026-09-25
Related: PLAN-host-installer-handover (DCT's side of the host-installer split), helpers-no/client-provisioning (the host installer), PLAN-windows-testing (broader Windows validation)
Problem​
1. initializeCommand is bash-only, and Windows runs it with cmd.exe (main defect)​
devcontainer-user-template.json has:
mkdir -p .devcontainer.secrets/env-vars && hostname -s > .devcontainer.secrets/env-vars/.host-hostname 2>/dev/null || hostname > .devcontainer.secrets/env-vars/.host-hostname 2>/dev/null || true
Mechanism (verified in upstream source): devcontainers/cli src/spec-node/utils.ts, runInitializeCommand (lines 559–560): on Windows a string command runs as [ComSpec || 'cmd.exe', '/c', <string>], elsewhere as ['/bin/sh', '-c', <string>]. A non-zero exit aborts container startup.
In cmd.exe, mkdir -p with forward slashes fails, the || chain ends at true, which does not exist in cmd.exe, and the command exits non-zero. So VS Code cannot start the container.
History (verified in git):
| Date | Commit | What happened |
|---|---|---|
| 2026-02-03 | c4d7f6c, 2078f13 | install.ps1 wrote a cmd.exe-specific initializeCommand, ending in & ver >nul to force exit code 0 |
| 2026-02-17 | d07e842 | One shared devcontainer-user-template.json for all platforms; the Windows command was dropped |
| 2026-04-07 | 733dd73 | A bash-only initializeCommand was added to the shared template (host hostname capture) |
Not yet reproduced on Windows — no Windows machine was available. The failure follows from the mechanism above; Phase 1's CI job and Phase 3 confirm it.
2. install.ps1 closes the user's PowerShell window on error​
The Quick Start runs it as irm … | iex, so the script runs inside the user's own session. exit 1 (used in three places: lines 22, 54, 61) therefore closes the user's terminal before they can read the error. $ErrorActionPreference = "Stop" also stays set in their session afterwards.
3. install.ps1 says "installed!" after a failed docker pull​
docker pull is a native command; its exit code is never checked. If Rancher Desktop is not running, the pull fails and the script still prints success.
4. The Quick Start block mixes Mac/Linux and Windows​
website/docs/index.md (and README.md) put the curl … | bash line and the irm … | iex line in one bash code block. The copy button copies both. In PowerShell 5.1 curl is an alias for Invoke-WebRequest, so the first line fails with a parameter error.
Phase 1: Cross-shell initializeCommand — IN PROGRESS​
Tasks​
-
1.1 Replace the template's
initializeCommandwith one string that is valid in both shells:ver || sh -c "mkdir -p .devcontainer.secrets/env-vars && { hostname -s 2>/dev/null || hostname; } > .devcontainer.secrets/env-vars/.host-hostname; true"cmd.exe:versucceeds (exit 0), so||skips the rest. The quoted part is one argument, socmd.exedoes not interpret the&&,>or{ }inside it./bin/sh:verdoes not exist, so||runs the capture, which ends intrue(exit 0).- Windows does not need the file:
config-host-info.shchecksDEV_HOST_COMPUTERNAME(from${localEnv:COMPUTERNAME}) before it reads the file (lines 76–87). - Measured on macOS 2026-09-25: exit 0 and the same
.host-hostnamecontent as today's command. The side effect is onever: command not foundline in the startup log.
-
1.2 CI: add a
windows-latestjob that readsinitializeCommandfromdevcontainer-user-template.jsonand runs it withcmd.exe /cin a temp folder. It must exit 0. Also run the Linux side with/bin/sh -cand check that.host-hostnameis non-empty. This guards against the regression coming back; nothing tested the Windows side before.- As built (2026-09-25): a separate workflow,
.github/workflows/host-commands.yml, notci-tests.yml.ci-tests.ymlonly triggers on.devcontainer/**and builds the whole image first, and the template lives at the repo root. The Windows job runs the command through the real devcontainers CLI (@devcontainers/cli@0.89.0,devcontainer up), so it takes the samecmd.exe /cpath, quoting included, as VS Code does. A control step runs the old bash-only command (.github/fixtures/host-commands/bash-only-initialize-command.json) and must see it fail, which proves the check can detect the bug. Script:.github/scripts/test-initialize-command.ps1. - First run on GitHub (PR #102, 2026-09-25): the new command passed through the real CLI on
windows-latest. The control did not fail:cmd.exeprinted "The syntax of the command is incorrect." and "The system cannot find the path specified.", but the old command still exited 0, because the runner has Git for Windows'usr\bin(withtrue.exe) on PATH. A normal office PC does not. The script now strips Git/MSYS/Cygwin Unix tool folders from PATH and refuses to run if a Unixtrueis still found. That also sharpens the bug: it hits PCs without Unix tools on PATH, which is the normal case.
- As built (2026-09-25): a separate workflow,
-
1.3 Add a comment next to the command (in the contributor docs, since JSON has no comments) saying it runs under
cmd.exeon Windows and must stay valid in both shells.- Done in
contributors/architecture/devcontainer-json.mdandstartup-lifecycle.md. Both still showed the old command and claimed.host-hostnameis written on Windows; corrected.
- Done in
Validation​
The CI job is green on windows-latest and ubuntu-latest. The command still works on macOS: .host-hostname is written.
Phase 2: install.ps1 is safe to run with irm | iex​
Tasks​
- 2.1 Wrap the script body in a scriptblock (
& { … }), so$ErrorActionPreferencestays local to it, and replace everyexit 1with an error message plusreturn. The user's window stays open and shows what went wrong.- Done 2026-09-25: the body runs in
& { ... }, everyexit 1is nowreturn. Tested in PowerShell withGet-Content install.ps1 -Raw | Invoke-Expression: the session survives every failure and the caller's$ErrorActionPreferenceis untouched. Exit codes for callers (PLAN-host-installer-handover 2.2) are not done yet: insideirm | iexthere is no exit code without closing the window, so that needs a separate, non-iexentry point.
- Done 2026-09-25: the body runs in
- 2.2 Confirmed on Terje's PC (urb-agents #1536, 2026-09-25): with Rancher Desktop not running,
docker pullfailed (failed to connect to the docker API at npipe:////./pipe/docker_engine) and the script still printed "devcontainer-toolbox installed!". Check$LASTEXITCODEafterdocker pull. On failure, stop without printing "installed!" and say what to do in plain words, for example: "Rancher Desktop is not running. Start Rancher Desktop from the Start menu, wait until it says it is ready, then run this again."- Done 2026-09-25: step 1 now checks, before anything is written, whether Rancher Desktop is installed (user and system install paths), visible to this window (
dockeron PATH), and running (docker info), each with a plain message saying what to do (Terje: "the user must be notified that rancher must be running"). A faileddocker pullstops without "installed!".install.shgot the same messages and now names Rancher Desktop instead of Docker Desktop. Tested with stubdockercommands in PowerShell and bash; not yet on Windows.
- Done 2026-09-25: step 1 now checks, before anything is written, whether Rancher Desktop is installed (user and system install paths), visible to this window (
- 2.3 Rewrite every message the script prints for a non-developer: no "PATH", "Docker CLI" or "image" without explanation; each error says what happened and the one thing to do next. The missing-Docker case uses the handover sentence from PLAN-host-installer-handover Phase 2.
- 2.4 Stop deleting the user's previous backup:
install.ps1removes an existing.devcontainer.backup/without asking (lines 30–31), so a second run loses the original setup. Refuse instead, with a plain message, the wayinstall.shalready does. Found by client-provisioning (urb-agents #1505). - 2.5 Install the VS Code Dev Containers extension as the user (
code --install-extension ms-vscode-remote.remote-containers), findingcodeeven when it is not on PATH yet (user and system install paths). If VS Code is missing, stop with a plain message. VS Code itself comes from Intune (Terje, 2026-09-25). The shared spec is PLAN-host-installer-handover task 2.8.- Done 2026-09-25, pulled forward for Terje's PC test, in
install.ps1andinstall.sh(step 4b). Findscodeon PATH, else in the user and system install folders (Windows) or the app bundle (macOS); skips whencode --list-extensionsalready lists it. Limit until 2.1: if VS Code is missing it prints a plain warning and continues, rather than exiting1, becauseexitinsideirm | iexwould close the user's window. - Checked:
install.shshellcheck clean; its step 4b run against a stubcode(installs when missing, skips when present) and, with nocodeon PATH, it found this Mac's real VS Code in the app bundle.install.ps1parses with 0 errors and has 0 PSScriptAnalyzer findings apart fromPSAvoidUsingWriteHost(the script has always usedWrite-Host). Not run on Windows yet.
- Done 2026-09-25, pulled forward for Terje's PC test, in
- 2.7 Refuse to install into a system or unsuitable folder. Found on Terje's PC (urb-agents #1536): he ran it in an administrator PowerShell, which opens in
C:\Windows\System32, and the script created.devcontainer\and.vscode\there. A non-developer does not know to change folders first. Refuse$env:SystemRootand anything under it,Program Files, and the drive root. For the user's home folder itself, offer a work folder instead (for example$HOME\DevContainer-Toolbox\<name>) rather than writing into it. Also warn when running elevated: nothing ininstall.ps1needs admin. - 2.6 CI: in the
windows-latestjob, runinstall.ps1in a temp folder withdockermissing fromPATH. It must print the Docker error and leave the PowerShell process running (the job's next step still executes).
Validation​
The CI job is green. The script still works end to end on a Windows machine (Phase 3).
Phase 3: Docs, and a real Windows run​
Tasks​
- 3.1 Split the Quick Start in
website/docs/index.mdandREADME.mdinto two blocks: abashblock for Mac/Linux and apowershellblock for Windows, so each copy button copies one command.- Done 2026-09-25 in
index.mdandREADME.md: Windows first, one block per platform, a plain "Before you start" line (Rancher Desktop started and ready, VS Code, Company Portal on a work PC), "not Run as administrator", and a sentence on what the installer does (including the extension).
- Done 2026-09-25 in
- 3.1b Correct the Windows prerequisites in
website/docs/getting-started.md: Rancher Desktop needs Windows 11 x64 (not Windows 10), andwsl --installalso installs Ubuntu, which asks for a Linux username the user does not need. Point to the "What your computer needs" page from PLAN-host-installer-handover when it exists.- Done 2026-09-25 in
getting-started.md(and README Prerequisites): Windows 11 x64; WSL via IT on a work PC, orwsl --install --no-distributionon your own; macOS 13+ on Apple Silicon; Rancher Desktop 1.24+, started before installing; the extension is no longer listed as a prerequisite; Step 1 explains making a project folder in a normal (not administrator) PowerShell. The "What your computer needs" page from PLAN-host-installer-handover does not exist yet, so these rows live in Getting Started for now.
- Done 2026-09-25 in
- 3.2 Needs a Windows machine (Terje, or someone he names): in an empty folder, run the Quick Start from the site, open it in VS Code, and choose "Reopen in Container". The container must start, and
dev-helpmust run.- Passed 2026-09-25 on Terje's managed Windows PC (urb-agents #1536), DCT 1.8.3. With Rancher Desktop stopped, the script said so and wrote nothing. With it running, it detected the already installed extension, pulled the image, and "Reopen in Container" started the container with the full startup sequence. The first attempt had run in an administrator PowerShell in
C:\Windows\System32(hence task 2.7). - Findings from that run, not blockers: (1) Host info is empty on Windows:
OS: unknown,User: unknown,Hostname: devcontainer(the fallback), although the docs say Windows gets the hostname fromCOMPUTERNAMEviaremoteEnv. The Docker Engine block did show the PC's real name, so a source exists. Belongs to INVESTIGATE-host-identity-and-template-defaults. (2) Git identity isvscode@localhost: nothing captures the host's git identity on Windows (older than 1.8.2). Belongs to INVESTIGATE-git-identity-auto-detect.
- Passed 2026-09-25 on Terje's managed Windows PC (urb-agents #1536), DCT 1.8.3. With Rancher Desktop stopped, the script said so and wrote nothing. With it running, it detected the already installed extension, pulled the image, and "Reopen in Container" started the container with the full startup sequence. The first attempt had run in an administrator PowerShell in
- 3.3 Have one non-developer office user do 3.2 from the site alone, with Rancher Desktop and VS Code already installed, and note every point where they got stuck. Pass those notes to the owner of helpers-no/client-provisioning through the bus.
- 3.4 Release: bump
version.txt(PATCH).
Validation​
Terje confirms 3.2 on Windows. npm run build passes for the docs change.
Acceptance Criteria​
- A fresh Windows install from the published Quick Start reaches a running container
-
initializeCommandis tested on Windows in CI -
install.ps1never closes the user's window, never reports success after a failed pull, and every message tells a non-developer what to do next - Each Quick Start copy button copies one platform's command
Files to Modify​
devcontainer-user-template.jsoninstall.ps1.github/workflows/ci-tests.yml(newwindows-latestjob)website/docs/index.md,README.md,website/docs/getting-started.mdwebsite/docs/contributors/architecture/devcontainer-json.md(note oninitializeCommand)version.txt
Not in this plan​
- PowerShell 5.1 on old Windows 10 builds may default to TLS 1.0 before the script's own TLS 1.2 line runs, so
irmitself could fail. Current Windows 10/11 is not affected; handle it in PLAN-windows-testing if it shows up.