Skip to content

fix(setup): explain WSL platform install download failures - #2

Closed
RomneyDa wants to merge 2 commits into
joelagnel:feature/local-ai-llama-server-code-onlyfrom
openclaw:fix/wsl-install-diagnostics
Closed

fix(setup): explain WSL platform install download failures#2
RomneyDa wants to merge 2 commits into
joelagnel:feature/local-ai-llama-server-code-onlyfrom
openclaw:fix/wsl-install-diagnostics

Conversation

@RomneyDa

@RomneyDa RomneyDa commented Aug 21, 2026

Copy link
Copy Markdown

Stacked on openclaw#1178 — base is that PR's branch, so this diff is only the three files below.

What broke

Setup failed on an ARM64 Windows 11 (build 28000) box at preflight-wsl:

step.completed: preflight-wsl → Failed  "WSL platform install failed with exit code 1."

That message is everything the engine knew. Tracing it took hours; the actual cause was one line wsl.exe printed and we discarded:

Downloading: Windows Subsystem for Linux 2.7.12
Forbidden (403).

C:\Windows\System32\wsl.exe is a stub — the real WSL ships out of band. When WSL is absent, wsl --install resolves the package to download by calling https://api.github.com/repos/Microsoft/WSL/releases/latest. That URL is embedded in the stub (it is the only URL in the binary), and this build has no --web-download / --from-store option strings, so nothing redirects it at the Store.

Unauthenticated GitHub API calls are capped at 60/hour per IP. On the failing machine:

X-RateLimit-Limit: 60   X-RateLimit-Remaining: 0   X-RateLimit-Used: 60

It is a remote-accessed lab machine behind shared egress, and the quota was pegged at 60/60 in two windows six hours apart — spent by unrelated traffic, not by OpenClaw. Corporate NAT, VPN, cloud hosts and CI runners are all exposed to the same thing. Note the cap is per IP, so a fleet provisioning behind one NAT shares one 60/hour bucket, and retries drain it faster.

Corroborating evidence that the install died before doing any work: dism.log untouched, zero AppXDeploymentServer/Operational events, both Microsoft-Windows-Subsystem-Linux and VirtualMachinePlatform still Disabled.

Why the error was invisible

InstallWslPlatformAsync must run the installer elevated, elevation requires ShellExecute, and ShellExecute cannot redirect stdout/stderr. The process exit code was the only signal — and exit 1 cannot distinguish a rate limit from a broken network, a policy block, or a real WSL fault.

The fix

Name the cause. Read the same quota endpoint wsl.exe depends on, and use it twice: before elevating, to fail without raising an administrator prompt for an install that cannot succeed; and after a non-zero exit, to name the likely cause. /rate_limit is itself exempt from the quota, so the probe is free, and a probe that fails returns null and never turns a recoverable failure into a hard one.

Then hand the user a way out. Naming the cause is not enough when the cause is someone else's traffic on a shared IP — the user still has to get WSL onto the box. Every failure message now ends with two concrete routes:

Install WSL yourself, then run setup again:
  1. Microsoft Store (works even while the GitHub quota is spent): https://aka.ms/wslstorepage
     or run: winget install --id 9P9TQF7MRM4R --source msstore
  2. Or, in an elevated PowerShell: wsl --install --no-distribution
Reboot if Windows asks for one.

The Store route is first because it does not touch the GitHub API, so it works during the window that defeats wsl --install — verified from the affected machine (winget show --id 9P9TQF7MRM4R --source msstore resolves while the API returns 403). This also composes with how setup already behaves: WslViabilityInspector is read-only and EnsureWslPlatformStep returns early when WSL is Ready, so a user who installs WSL by any means simply walks past both steps on the next run.

The failure surface needs no changes — CompletePage renders the message in a wrapping monospace card and lifts the first URL into a clickable button, so the line breaks and bare commands survive intact.

ensure-wsl-platform also becomes retryable — inspection and wsl --install are both idempotent, and the failures seen here (exhausted quota, declined elevation prompt) are transient.

Before / after on the failing machine:

message
before WSL platform install failed with exit code 1.
after WSL is not installed, and OpenClaw cannot install it right now: wsl --install downloads WSL from GitHub, and this machine has already used its full unauthenticated GitHub API quota (60/60), which resets at 17:19 local time. Shared networks reach that cap without any help from OpenClaw. + the self-install block above

Verification

  • 946/946 OpenClaw.SetupEngine.Tests pass — 933 existing plus 13 new covering the wording, the exhaustion predicate, the self-install routes, and the pre-elevation abort (asserting the probe runs exactly once, i.e. no elevation prompt is raised).
  • Live smoke test of QueryGitHubQuotaAsync against the real endpoint on the failing machine: limit=60 remaining=0 used=60 resets=17:19 exhausted=True, matching the raw API exactly.

Deliberately not done

  • Capturing the installer's real stdout/stderr. Possible via an elevated cmd /c "… > out 2> err" wrapper — I verified the quoting survives ShellExecute — but it makes the UAC prompt read "Windows Command Processor" instead of wsl.exe, and puts an elevated process writing into a user-writable path. A signed self-elevating helper is the right long-term answer.
  • Installing WSL from a pinned, hash-verified .msixbundle, reusing LocalAiArtifactInstaller and PinnedArtifact the way the CUDA runtime and GGUF models already do. This is the real fleet fix — it removes the GitHub API from the provisioning path entirely and pins one WSL version across machines — but it is a behaviour change deserving its own review, and the MSIX install step is unverified.

Adjacent defects, not fixed here

  • CommandRunner.RunAsync returns -1 on timeout, discarding the process's real exit code (CommandRunner.cs:192). That file is not touched by feat: add managed local AI with llama-server openclaw/openclaw-windows-node#1178.
  • The 5s budget for the wsl --version probe is too tight for a cold first invocation — measured 26-31ms warm, but the failing run burned the full 5s and recorded a synthetic timeout. It also gates the Capabilities page's Next click through InitializeLocalAiReviewAsync, producing a visible UI hang when WSL is absent.
  • CompletePage.xaml.cs:80 labels the help link "Update WSL →" whenever the message contains "WSL"; "Install WSL →" would read better in this case.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L8LRNTZ5fBgjGwmtBCrSp3

@joelagnel
joelagnel force-pushed the feature/local-ai-llama-server-code-only branch 2 times, most recently from 1e4c2f7 to 2bcf986 Compare August 21, 2026 00:23
RomneyDa and others added 2 commits August 20, 2026 17:30
wsl.exe in System32 is a stub; the real WSL ships out of band. When WSL is
absent, wsl --install resolves the package to download by calling
https://api.github.com/repos/Microsoft/WSL/releases/latest — a URL embedded
in the stub, with no command-line option to redirect it at the Microsoft
Store. Unauthenticated GitHub API calls are capped at 60/hour per IP, so a
machine behind shared egress (corporate NAT, VPN, cloud hosts, CI,
remote-accessed lab hardware) can find the quota already spent by unrelated
traffic. wsl.exe then prints "Forbidden (403)." and exits 1.

Setup could report none of that. The installer must run elevated, elevation
requires ShellExecute, and ShellExecute cannot redirect stdout/stderr, so
the only thing reaching the log was "WSL platform install failed with exit
code 1" — with no way to tell a rate limit from a broken network, a policy
block, or a genuine WSL fault.

Read the same quota endpoint wsl.exe depends on, and use it twice: before
elevating, to fail without raising an administrator prompt for an install
that cannot succeed, and after a non-zero exit, to name the likely cause.
/rate_limit is itself exempt from the quota, so the probe is free, and a
probe that fails never turns a recoverable failure into a hard one. Both
messages point at the Microsoft Store, which does not go through the
GitHub API.

ensure-wsl-platform becomes retryable: inspection and wsl --install are
both idempotent, and the failures seen here — an exhausted quota, a
declined elevation prompt — are transient.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8LRNTZ5fBgjGwmtBCrSp3
Naming the cause is not enough when the cause is someone else's traffic on
a shared IP: the user still has to get WSL onto the machine. Every failure
message now ends with two concrete routes, in order of what works while the
GitHub quota is still spent.

The Microsoft Store route is listed first precisely because it does not go
through the GitHub API, so it succeeds during the window that defeats
wsl --install. It carries both the Store page and a winget one-liner, so
the fix is copyable rather than a hunt. The elevated wsl --install command
is second, for once the quota frees up.

The failure surface already cooperates: CompletePage renders the message in
a wrapping monospace card and lifts the first URL into a clickable link, so
the line breaks and bare commands survive to the user unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8LRNTZ5fBgjGwmtBCrSp3
@RomneyDa
RomneyDa force-pushed the fix/wsl-install-diagnostics branch from 2bf1be3 to 9826445 Compare August 21, 2026 00:31
@joelagnel

Copy link
Copy Markdown
Owner

Rewritten compactly and safely, with Dallin's authorship preserved, in the official OpenClaw PR openclaw#1178: openclaw#1178

The replacement implementation is commit 1458856. Closing this PR as superseded.

@joelagnel joelagnel closed this Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants