Place the vLLM Qwen project inside WSL2 automatically on Windows - #5834
Merged
Conversation
Setting up vLLM (Qwen3.8-27B) on a Windows host used to dead-end: PortOS refused to clone, printed a UNC template with `<distro>` and `<user>` left as literal angle brackets, and told the operator to set VLLM_QWEN_PROJECT_DIR and click the button again. Every value in that template is something the machine can answer. PortOS now asks WSL for them. Before cloning, it runs the default distro's own shell (`wsl.exe -e sh -c 'echo "$WSL_DISTRO_NAME"; echo "$HOME"'` -- the distro's stdout is UTF-8, while `wsl --list` prints UTF-16LE that a UTF-8 reader mangles), checks the derived \wsl.localhost path is readable from Windows, and records it as VLLM_QWEN_PROJECT_DIR in PortOS's own .env so the readiness poll, the Start button, and the next server boot all resolve the same directory. Start does the same, so a project prepared by hand inside the distro is found with no configuration at all. It refuses only where it genuinely cannot answer: no WSL on the host, no distro but a container engine's own (docker-desktop is wiped on a reset), or a \wsl.localhost share Windows cannot read -- and each refusal names that host's fix instead of a placeholder. Setting the env var still overrules the whole decision. Also fixes a misleading progress line: a provisioning click reported "already installed - starting it" immediately before a clone and a ~30 GB download, which made every line after it read as a failed start. - server/lib/wslDistro.js: new; distro/home detection and UNC derivation - server/lib/vllmQwenProject.js: record and resolve the project directory, anchored to the install root so a worktree boot writes where the install reads - server/lib/vllmQwenProvision.js: upsertEnvLine, sharing one newline guard with mergeEnvFileContents - server/services/vllmQwenManager.js: settle placement before provision and start - server/services/localRuntimeSetup.js: each provisioning step says what it is about to do
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Setting up vLLM (Qwen3.8-27B) on a Windows host used to dead-end in the readiness modal:
Two things are wrong there. The angle brackets are literal — the operator has to
go find those two values themselves — and every one of them is something the
machine can answer. And the line above it claims a start that never happens,
which makes the refusal read as a failed start.
PortOS now asks WSL. Before cloning, it runs the default distro's own shell
(
wsl.exe -e sh -c 'echo "$WSL_DISTRO_NAME"; echo "$HOME"'), verifies the derived\\wsl.localhost\…path is readable from Windows, and records it asVLLM_QWEN_PROJECT_DIRin PortOS's own.env— so the once-a-minute readinesspoll, the Start button, and the next server boot all resolve the same directory
the provisioning run actually used. The Start button settles placement too, so a
project prepared by hand inside the distro (the documented path, and the only
one SGLang has) is found with no configuration at all.
The probe deliberately runs the distro's shell rather than
wsl --list:wsl.exe'sown UI output is UTF-16LE, which a UTF-8 reader mangles, while the stdout of a
program it executes passes through byte for byte. The UTF-16 read is confined to
the failure path, where the distro list only decorates an error message.
It refuses only where it genuinely cannot answer, and each refusal names that
host's fix instead of a placeholder:
no-wslwsl.exedid not run here — install a distro withwsl --install -d Ubuntuno-distrointernal-distrodocker-desktop, which is wiped on a reset — names a real oneunreadable-sharewsl --shutdownSetting
VLLM_QWEN_PROJECT_DIRyourself still overrules the whole decision.Changes
server/lib/wslDistro.js(new) —detectWslProjectDir(leaf); distro/homedetection, UNC derivation, and the share-readability check. Parameterized by
leaf name, so it is not vLLM-specific.
server/lib/vllmQwenProject.js— records and resolves the projectdirectory. Anchored to
PATHS.installRoot, notroot, so a server booted froma CoS worktree writes where the real install reads (Boot migrations resolve data root from executing-file location — crash when run from a CoS agent worktree #1947). Resolution order:
VLLM_QWEN_PROJECT_DIR(this run's decision) → the record →~/qwen-serving,owned by one exported
vllmProjectDirIsSettled()so the manager neverre-lists it.
server/lib/vllmQwenProvision.js—upsertEnvLine, the replacingcomplement to the additive-by-contract
mergeEnvFileContents; both now shareone trailing-newline guard. Its replacement is a function, not a string, so a
value carrying
$&is written literally.server/services/vllmQwenManager.js— settles placement before bothprovision and start; refusal prose is a
reason → lead sentencetable with oneshared tail, so a fifth reason cannot ship without the "PortOS will not fall
back to
C:\" sentence.server/services/localRuntimeSetup.js— each provisioning step now sayswhat it is about to do, instead of announcing a start before a ~30 GB download.
docs/features/qwen38-rtx3090.md— §1c is now "Windows, optional".Test plan
server/lib/wslDistro.test.js(new) — probe parsing, UTF-16 list decoding, UNCderivation, and each of the four refusal reasons, all through injected
run/existsso no subprocess is spawned.server/lib/vllmQwenProject.test.js— the record round-trips, an exported envvar outranks it, a second
recordVllmProjectDirrewrites rather than appends,and
vllmProjectDirIsSettledagrees with the resolver.envPathis threadedthrough so a sandbox answers rather than the developer's own install.
server/lib/vllmQwenProvision.test.js—upsertEnvLinereplaces, appendswithout splicing, and writes a
$-pattern value literally.server/services/vllmQwenManager.test.js— Windows detects and records; asettled directory spends no subprocess; a failed record still leaves the run on
the detected directory; each refusal reaches the caller; Start settles placement
before looking for the project.
verified identical on
origin/main— this machine has no Python, which thosesuites shell out to.
Follow-ups filed
hand-preparation is its only path; lift the settle→detect→record loop.
.envkey read/upsert implementations still duplicate thisone, and they disagree on
$-escaping and on.env-vs-process.envprecedence.