Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Windows first-run no longer reports "Shared runtime setup failed" when all
Windows actually needs is a restart. The signed-in pass now blocks on the
elevated process handle and waits for a terminal status: `Start-Process
-Verb RunAs -Wait` can return while the elevated child is still enabling
WSL features, and probing for a WSL runtime in that window reported a
failure the Captain could not act on.
- A reboot Windows has not taken yet is now recognised as `restart_required`
rather than a failure, including the `EnablePending` feature state, DISM's
ambiguous `Possible` restart flag, an absent `vmcompute` service, and a
pending Component Based Servicing restart.
- The setup card no longer paints a pending restart in the error colour, and
gives plain numbered steps: restart the PC, sign back in as the same user,
reopen 1Helm and setup resumes where it left off.

## [0.0.37] - 2026-08-02

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@
document.querySelectorAll('meta[name="theme-color"]').forEach(function (m) { m.setAttribute("content", color); });
})();
</script>
<link rel="stylesheet" href="/app.css?v=61ad9ced52a1" />
<link rel="stylesheet" href="/app.css?v=4b06d46cc8d7" />
<link rel="stylesheet" href="/bundle.css" />
<link rel="stylesheet" href="/excalidraw/index.css" />
</head>
<body class="h-screen w-screen overflow-hidden antialiased">
<div id="app" class="h-full w-full"></div>
<script type="module" src="/bundle.js?v=5929b5ad167a"></script>
<script type="module" src="/bundle.js?v=4e291851eb16"></script>
</body>
</html>
43 changes: 40 additions & 3 deletions scripts/install-wsl-runtime.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,27 @@ function Test-WslRestartFailure {
}

function Require-WindowsRestart {
$message = "WSL 2 features are enabled. Restart Windows once, then retry 1Helm computer setup."
Write-SetupStatus -Status "restart_required" -Step $message -Progress 20 -ErrorMessage "Windows restart required to finish enabling WSL 2."
$message = "Restart this PC to finish enabling WSL 2, then open 1Helm again. Setup continues automatically."
Write-SetupStatus -Status "restart_required" -Step $message -Progress 20 -ErrorMessage "Windows must restart to finish enabling WSL 2. No other action is needed."
Write-Host $message
exit 10
}

# Windows cannot activate the WSL 2 features until it reboots: the features sit
# in EnablePending, DISM reports RestartRequired as the ambiguous "Possible",
# and the vmcompute service does not exist yet. Any of those is a reboot, not a
# failure, and must never be reported to the Captain as a broken installation.
function Test-PendingWslRestart {
if ($null -eq (Get-Service -Name vmcompute -ErrorAction SilentlyContinue)) { return $true }
foreach ($name in @("Microsoft-Windows-Subsystem-Linux", "VirtualMachinePlatform")) {
$feature = Get-WindowsOptionalFeature -Online -FeatureName $name -ErrorAction SilentlyContinue
if ($null -eq $feature) { continue }
if ([string]$feature.State -ne "Enabled") { return $true }
if (Test-RestartRequired $feature) { return $true }
}
return (Test-Path -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending')
Comment on lines +85 to +93

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'function Test-RestartRequired|RestartRequired|Test-PendingWslRestart' scripts/install-wsl-runtime.ps1 test

Repository: gitcommit90/1Helm

Length of output: 6830


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- helper and pending-restart logic ---'
sed -n '55,96p' scripts/install-wsl-runtime.ps1
printf '%s\n' '--- relevant regression assertions ---'
sed -n '175,198p' test/desktop.mjs
printf '%s\n' '--- available PowerShell runtimes ---'
command -v pwsh || true
command -v powershell || true

printf '%s\n' '--- static behavioral probe of the helper conditions ---'
python3 - <<'PY'
from pathlib import Path
import re

text = Path("scripts/install-wsl-runtime.ps1").read_text()
match = re.search(
    r'function Test-RestartRequired\s*\{(?P<body>.*?)\n\}',
    text,
    re.S,
)
if not match:
    raise SystemExit("Test-RestartRequired not found")
body = match.group("body")
print(body.strip())
for value in ("Required", "Possible", "1", "True", "False", "None"):
    accepted = any(f'"{value}"' in body for _ in [0])
    print(f"{value}: {'accepted by source conditions' if accepted else 'not accepted by source conditions'}")
PY

Repository: gitcommit90/1Helm

Length of output: 5300


🌐 Web query:

Microsoft Get-WindowsOptionalFeature RestartRequired Possible enum official documentation

💡 Result:

The RestartRequired property returned by the Get-WindowsOptionalFeature PowerShell cmdlet corresponds to the DismRestartType enumeration defined in the Microsoft.Dism.Commands namespace [1][2]. According to official Microsoft documentation for the DismRestartType enumeration, the possible values are [1]: - No (0): No restart is required [1]. - Possible (1): This package or feature might require a restart [1]. - Required (2): This package or feature always requires a restart [1]. In the context of the Get-WindowsOptionalFeature output, these values indicate whether a system restart is necessitated after enabling or disabling a specific optional feature [1][3]. Note that this property is distinct from the RestartNeeded property sometimes seen on objects returned by DISM-related cmdlets; RestartRequired describes the requirement inherent to the feature, while RestartNeeded typically indicates whether a restart is currently pending on the system [2].

Citations:


Treat RestartRequired = "Possible" as a pending restart.

Test-RestartRequired does not accept "Possible". If both features are Enabled, vmcompute exists, and CBS has no pending-restart key, Test-PendingWslRestart can report failure instead of restart_required.

Update the helper and add a regression case for "Possible".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install-wsl-runtime.ps1` around lines 85 - 93, Update
Test-RestartRequired to treat RestartRequired equal to "Possible" as requiring a
restart, alongside its existing restart-required values. Add a regression test
covering the "Possible" value and verify Test-PendingWslRestart reports a
pending restart when both features are enabled, vmcompute exists, and no CBS
reboot key is present.

}

function Get-WslDistributionNames {
$result = Get-WslText -ArgumentList @("--list", "--quiet")
if ($result.ExitCode -ne 0) { return @() }
Expand Down Expand Up @@ -261,15 +276,37 @@ try {
$hostArguments += @("-StatusPath", ('"{0}"' -f $statusArg))
}
$hostProcess = Start-Process -FilePath "powershell.exe" -ArgumentList ($hostArguments -join " ") -Verb RunAs -Wait -PassThru
# -Wait is not dependable for an elevated ShellExecute launch: it can return
# while the child is still enabling Windows features. Continuing here probes
# for a WSL runtime the child has not finished installing and reports a false
# failure, so block on the real process handle before reading any outcome.
if ($null -ne $hostProcess) {
try { $hostProcess.WaitForExit() } catch { }
}
$hostExitCode = if ($null -eq $hostProcess) { $null } else { $hostProcess.ExitCode }
# The status file is written by the child immediately before it exits. Give a
# bounded grace period for a terminal record rather than racing its last write.
$settleDeadline = (Get-Date).AddSeconds(30)
while ((Get-Date) -lt $settleDeadline) {
$pending = Read-ReportedSetupStatus
if ($null -ne $pending -and @("restart_required", "failed", "complete") -contains [string]$pending.status) { break }
Start-Sleep -Milliseconds 500
}
$hostOutcome = Get-HostSetupOutcome -ExitCode $hostExitCode
if ($hostOutcome.Status -eq "restart_required") {
Write-SetupStatus -Status "restart_required" -Step $hostOutcome.Step -Progress 20 -ErrorMessage $hostOutcome.Detail
Write-Host $hostOutcome.Step
exit 10
}
if ($hostOutcome.Status -eq "failed") { Fail-Setup $hostOutcome.Detail }
if ($hostOutcome.Status -eq "failed") {
# A reboot that Windows has not taken yet is the single most common reason
# the elevated pass cannot finish. Tell the Captain to restart instead of
# presenting a failed installation they cannot act on.
if (Test-PendingWslRestart) { Require-WindowsRestart }
Fail-Setup $hostOutcome.Detail
}
if (-not (Test-PinnedWslRuntime)) {
if (Test-PendingWslRestart) { Require-WindowsRestart }
Fail-Setup "Microsoft WSL $wslVersion is not ready in the signed-in user's session."
}

Expand Down
22 changes: 16 additions & 6 deletions src/client/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,22 +156,32 @@ export function openOnboarding(root: HTMLElement, opts: WizardOptions): void {
h("div", { class: "font-semibold text-fg" }, restart ? "Windows restart required" : failed ? "Shared runtime setup failed" : "Setting up shared Windows runtime"),
h("p", { class: "mt-2 text-sm leading-6 text-muted" },
restart
? "Windows enabled WSL 2 components that need a reboot. Restart the PC, reopen 1Helm, then create the workspace again."
? "Windows finished enabling WSL 2, which needs a restart before it can run. Nothing went wrong and nothing is lost."
: "One-time administrator setup. Keep the PowerShell window open until it finishes; 1Helm tracks progress here."),
h("div", { class: "wizard-progress mt-4" }, h("span", { style: `width:${width}%` })),
h("p", { class: "mt-3 text-sm leading-6 text-fg" }, setup.step || "Working…"),
setup.error ? h("p", { class: "mt-2 text-sm text-danger" }, setup.error) : null,
h("p", { class: "mt-2 text-xs text-muted" }, failed || restart
? "After a successful setup, 1Helm will prepare the sealed channel image automatically."
: "This downloads Microsoft's pinned WSL package and the shared Linux runtime, then installs Podman inside it."),
// A pending restart is an expected step, not a fault: never paint it in
// the danger colour that tells the Captain their installation broke.
setup.error ? h("p", { class: `mt-2 text-sm ${restart ? "text-muted" : "text-danger"}` }, setup.error) : null,
restart
? h("ol", { class: "mt-3 list-decimal space-y-1 pl-5 text-sm leading-6 text-fg" },
h("li", {}, "Restart this PC."),
h("li", {}, "Sign back in as the same Windows user."),
h("li", {}, "Open 1Helm and continue — setup picks up where it left off."))
: null,
h("p", { class: "mt-2 text-xs text-muted" }, restart
? "1Helm keeps this progress. After the restart it finishes the runtime and prepares the sealed channel image automatically."
: failed
? "After a successful setup, 1Helm will prepare the sealed channel image automatically."
: "This downloads Microsoft's pinned WSL package and the shared Linux runtime, then installs Podman inside it."),
];
if ((failed || restart) && opts?.retry) {
children.push(h("button", {
class: "btn-primary mt-3 w-full py-2 sm:w-auto",
onclick: () => { opts.retry?.(); },
}, restart ? "I restarted — retry setup" : "Retry shared runtime setup"));
}
status.replaceChildren(h("div", { class: `card p-4 ${failed || restart ? "border-danger/40" : "border-accent/30"}` }, ...children));
status.replaceChildren(h("div", { class: `card p-4 ${failed ? "border-danger/40" : "border-accent/30"}` }, ...children));
};

const runWindowsRuntimeSetup = async (button: HTMLButtonElement): Promise<void> => {
Expand Down
14 changes: 14 additions & 0 deletions test/desktop.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,20 @@ test("desktop entrypoint keeps the renderer sandboxed and data on the Mac", asyn
assert.match(windowsRemoval, /"--exec", "\/usr\/libexec\/1helm-oci-runtime"/, "Windows removal delegates ownership checks to the narrow installed OCI helper");
assert.match(windowsRuntime, /VirtualMachinePlatform/);
assert.match(windowsRuntime, /2\.7\.10\.0/);
// A pending Windows reboot must never surface to the Captain as a broken
// installation: -Wait can return while the elevated child is still enabling
// features, and probing for WSL in that window reports a false failure.
assert.match(windowsRuntime, /\$hostProcess\.WaitForExit\(\)/, "the signed-in pass blocks on the real elevated process handle, not only Start-Process -Wait");
assert.match(windowsRuntime, /function Test-PendingWslRestart/, "the installer can recognise a reboot Windows has not taken yet");
assert.match(windowsRuntime, /vmcompute[\s\S]{0,400}EnablePending|EnablePending[\s\S]{0,400}vmcompute|-ne "Enabled"/, "pending feature activation counts as a restart, not a failure");
assert.match(windowsRuntime, /if \(Test-PendingWslRestart\) \{ Require-WindowsRestart \}[\s\S]{0,200}Fail-Setup "Microsoft WSL \$wslVersion is not ready/, "the user-session WSL probe asks for a restart before declaring failure");
assert.match(windowsRuntime, /Restart this PC to finish enabling WSL 2/, "the restart status carries plain-language instructions");
assert.match(windowsRuntime, /-Status "restart_required"/, "the restart path reports the dedicated restart status");
assert.match(windowsRuntime, /@\("restart_required", "failed", "complete"\) -contains/, "the parent waits for a terminal child status instead of racing its last write");
const onboardingRuntimeUi = await readFile(join(root, "src", "client", "onboarding.ts"), "utf8");
assert.match(onboardingRuntimeUi, /restart \? "text-muted" : "text-danger"/, "a pending restart is not painted as an error");
assert.match(onboardingRuntimeUi, /\$\{failed \? "border-danger\/40" : "border-accent\/30"\}/, "only real failures get the danger card treatment");
assert.match(onboardingRuntimeUi, /Restart this PC\./, "the restart state gives the Captain explicit numbered steps");
assert.match(windowsRuntime, /github\.com\/microsoft\/WSL\/releases\/download\/2\.7\.10\/wsl\.2\.7\.10\.0\.x64\.msi/);
assert.match(windowsRuntime, /1a62f90a43c03cc5bda47dfd0b6faf496ac70fd4389190518120a4f84fc895cf/);
assert.match(windowsRuntime, /Get-AuthenticodeSignature/);
Expand Down
Loading