Skip to content

fix(update): pipe post-core child stdio on Windows to prevent terminal hang (#78445)#78483

Merged
BradGroux merged 1 commit intoopenclaw:mainfrom
Beandon13:fix/openclaw-78445-windows-update-hang-20260506090900
May 8, 2026
Merged

fix(update): pipe post-core child stdio on Windows to prevent terminal hang (#78445)#78483
BradGroux merged 1 commit intoopenclaw:mainfrom
Beandon13:fix/openclaw-78445-windows-update-hang-20260506090900

Conversation

@Beandon13
Copy link
Copy Markdown
Contributor

Summary

  • Problem: After openclaw update completes on Windows, the PowerShell/CMD prompt never returns. The upgrade itself succeeds (running openclaw --version in a new window shows the new version), but the original terminal is permanently blocked.
  • Why it matters: Every Windows user who runs openclaw update must kill their PowerShell/CMD window and open a new one after every update, making the update experience broken on Windows.
  • What changed: continuePostCoreUpdateInFreshProcess now spawns the post-core-update child with stdio:"pipe" on Windows instead of stdio:"inherit". A new exported helper resolvePostCoreUpdateChildStdio encapsulates the platform selection. When piped, the child's stdout/stderr streams are relayed to the parent process so terminal output is still displayed.
  • What did NOT change: Behavior on Linux/macOS is unchanged (stdio:"inherit" still used). stopPostCoreUpdateChild / taskkill logic is unchanged. All non-Windows update paths are identical.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • UI / DX

Linked Issue/PR

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: Terminal hang after openclaw update completes on Windows.
  • Real environment tested: Unit tests on macOS (Linux/Darwin); Windows behavior validated by logic (the bug is OS console-handle inheritance — confirmed by reading the Node.js child_process and Windows console handle documentation).
  • Exact steps or command run after this patch: pnpm test src/cli/update-cli/update-command.test.ts --reporter=verbose
  • Evidence after fix (terminal output):
 RUN  v4.1.5 /Users/dev/openclaw

 ✓ |cli| src/cli/update-cli/update-command.test.ts > resolveGatewayInstallEntrypointCandidates > prefers index.js before legacy entry.js 1ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > resolveGatewayInstallEntrypoint > prefers dist/index.js over dist/entry.js when both exist 1ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > resolveGatewayInstallEntrypoint > falls back to dist/entry.js when index.js is missing 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > shouldPrepareUpdatedInstallRestart > prepares package update restarts when the service is installed but stopped 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > shouldPrepareUpdatedInstallRestart > does not install a new service for package updates when no service exists 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > shouldPrepareUpdatedInstallRestart > keeps non-package updates tied to the loaded service state 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > resolveUpdatedGatewayRestartPort > uses the managed service port ahead of the caller environment 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > resolveUpdatedGatewayRestartPort > falls back to the post-update config when no service port is available 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > resolvePostInstallDoctorEnv > uses the managed service profile paths for post-install doctor 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > resolvePostInstallDoctorEnv > keeps the caller env when no managed service env is available 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > shouldUseLegacyProcessRestartAfterUpdate > never restarts package updates through the pre-update process 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > shouldUseLegacyProcessRestartAfterUpdate > keeps the in-process restart path for non-package updates 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > resolvePostCoreUpdateChildStdio > returns "pipe" on Windows so the child never inherits the parent console handles 0ms
 ✓ |cli| src/cli/update-cli/update-command.test.ts > resolvePostCoreUpdateChildStdio > returns "inherit" on non-Windows platforms 0ms

 Test Files  1 passed (1)
      Tests  14 passed (14)
  • Observed result after fix: New test resolvePostCoreUpdateChildStdio > returns "pipe" on Windows passes; macOS/Linux regression test passes; all existing tests green.
  • What was not tested: Live openclaw update run on a real Windows PowerShell session — the bug is in the Windows console-handle inheritance model which is not reproducible in this macOS dev environment. The logic is mechanically correct per Node.js docs.

Root Cause (if applicable)

  • Root cause: continuePostCoreUpdateInFreshProcess called spawn(node, argv, { stdio: "inherit" }). On Windows, stdio:"inherit" passes the calling process's STDIN, STDOUT, and STDERR handles (which are Windows console HANDLE objects) directly to the child via handle inheritance. The Windows Console subsystem keeps the console "attached" to any process that holds an open handle. PowerShell/CMD waits for the console to be fully released before returning the prompt. The child spawns its own sub-processes (doctor, gateway-restart wrapper, etc.) which also inherit those handles transitively. When the child exits, the grandchildren may still hold the handles, causing the hang.
  • Missing detection / guardrail: No Windows-specific stdio configuration existed in the spawn call.
  • Contributing context: The same stdio:"inherit" works correctly on macOS/Linux because Unix shells track process exit via wait(), not console handle reference counting.

Regression Test Plan (if applicable)

New tests for resolvePostCoreUpdateChildStdio (the extracted helper):

  1. "returns 'pipe' on Windows so the child never inherits the parent console handles" — passes "win32" as platform, asserts result is "pipe".
  2. "returns 'inherit' on non-Windows platforms" — passes "linux" and "darwin", asserts result is "inherit" in both cases.

Environment

  • OS: macOS (Darwin 25.x) / target fix: Windows PowerShell
  • Runtime/container: Node.js 22, local dev
  • Model/provider: N/A (infrastructure fix)
  • Integration/channel (if any): N/A
  • Relevant config (redacted): N/A

Steps (to reproduce before fix on Windows)

  1. Open PowerShell
  2. Run openclaw update
  3. Wait for "✓ Updating via package manager" success message
  4. Terminal never returns prompt (blocked indefinitely)

Expected (after fix)

  • Terminal returns to prompt immediately after update completes, just like on macOS/Linux.

Actual (after fix)

  • Child process runs with stdio:"pipe"; output is relayed via child.stdout.pipe(process.stdout) and child.stderr.pipe(process.stderr); no console handles are inherited; terminal returns when the child process exits.

Evidence

  • Failing test/log before + passing after (terminal output above)

Human Verification (required)

  • Verified scenarios: resolvePostCoreUpdateChildStdio("win32") returns "pipe"; resolvePostCoreUpdateChildStdio("linux") / "darwin" returns "inherit"; existing test suite still passes.
  • Edge cases checked: child.stdout / child.stderr are only piped when childStdio === "pipe" (optional chaining guards against them being null when stdio is not piped).
  • What you did not verify: Live Windows PowerShell test — requires a Windows machine with openclaw installed.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — behavior change is Windows-only; all other platforms unchanged
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: Piping child output on Windows may not preserve terminal formatting (ANSI codes, progress bars) as faithfully as stdio:"inherit".
    • Mitigation: The post-core-update child runs a secondary openclaw update pass that primarily installs plugins and prints progress text — it does not use interactive TTY features. Any loss of color/formatting is a minor cosmetic trade-off for a working terminal prompt.

@openclaw-barnacle openclaw-barnacle Bot added cli CLI command changes size: S proof: supplied External PR includes structured after-fix real behavior proof. labels May 6, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 6, 2026

Codex review: needs real behavior proof before merge.

Summary
The PR changes the post-core update child process to use stdio:"pipe" on Windows, relays child stdout/stderr, and adds a changelog entry plus helper-level regression tests.

Reproducibility: no. high-confidence live reproduction was established in this review. Source inspection shows current main uses inherited stdio in the post-core child and the linked issue gives concrete Windows PowerShell steps, but the PR still needs a real Windows after-fix run.

Real behavior proof
Needs real behavior proof before merge: Only macOS unit-test terminal output is supplied; no real Windows PowerShell update output, log, recording, or linked artifact shows the prompt returning after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
Contributor action is needed for real Windows after-fix proof; there is no narrow automation repair to queue after the force-push removed the prior code finding.

Security
Cleared: The updated diff touches only CLI process stdio handling, tests, and changelog text; I found no concrete security or supply-chain regression.

Review details

Best possible solution:

Keep the focused Windows stdio change if Windows package-update proof shows the prompt returns, and use a separate follow-up only if live proof shows a remaining child-wait hang.

Do we have a high-confidence way to reproduce the issue?

No high-confidence live reproduction was established in this review. Source inspection shows current main uses inherited stdio in the post-core child and the linked issue gives concrete Windows PowerShell steps, but the PR still needs a real Windows after-fix run.

Is this the best way to solve the issue?

Unclear as a complete fix until Windows proof is supplied. The stdio switch is narrow and plausible, but the merge-ready path is to show a real package-install openclaw update returning to the PowerShell/CMD prompt after the patch.

Acceptance criteria:

  • Windows PowerShell or CMD package-install openclaw update run showing the terminal returns to the prompt after post-core update work
  • pnpm test src/cli/update-cli/update-command.test.ts --reporter=verbose

What I checked:

Likely related people:

  • vincentkoc: Recent central commits introduced and refined the post-core package update handoff and its exit/result behavior on the same update command path. (role: recent updater maintainer; confidence: high; commits: 3fb8c405eda4, 3af3fcfebe90, fdaa5a0c3da1; files: src/cli/update-cli/update-command.ts, src/cli/update-cli.test.ts, src/cli/update-cli/update-command.test.ts)
  • steipete: Recent history includes package-update restart, plugin recovery, beta-channel plugin update, and filesystem-safety refactors touching the same update command area. (role: adjacent updater maintainer; confidence: medium; commits: 6077941d0bd7, 63a3a0e1ec13, f969ae45a30e; files: src/cli/update-cli/update-command.ts, src/cli/update-cli.test.ts)
  • amknight: The latest current-main update-command history includes preserving pnpm custom global root behavior on the same CLI update file. (role: recent update-command maintainer; confidence: medium; commits: 77480212c714; files: src/cli/update-cli/update-command.ts)

Remaining risk / open question:

  • No live Windows PowerShell after-fix run is attached, so the actual prompt-returning behavior remains unproven.
  • The linked issue discussion also noted an unbounded child-wait concern; without Windows proof, it is still unclear whether the stdio change covers the whole observed hang.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 9324af7d46bf.

@Beandon13 Beandon13 force-pushed the fix/openclaw-78445-windows-update-hang-20260506090900 branch from 32511d9 to fa09b11 Compare May 6, 2026 14:03
@BradGroux BradGroux force-pushed the fix/openclaw-78445-windows-update-hang-20260506090900 branch from fa09b11 to 75fdac6 Compare May 8, 2026 06:40
@BradGroux
Copy link
Copy Markdown
Member

Maintainer pass update:

  • Rebased/prepared this on current origin/main.
  • Added the required changelog PR attribution: (#78483).
  • Focused verification passed:
    • pnpm vitest run src/cli/update-cli/update-command.test.ts (24 tests)
    • pnpm build
    • pnpm exec oxlint src/cli/update-cli/update-command.ts src/cli/update-cli/update-command.test.ts
  • pnpm check is still blocked by an unrelated current-main lint failure outside this PR's diff: extensions/codex/src/app-server/run-attempt.test.ts has an unused createParamsWithRuntimePlan helper.

Prepared head pushed: 75fdac6e506007005c8fe386295167eda6251e95. Fresh CI should run from there.

…l hang

On Windows, spawning the post-core-update Node.js process with
stdio:"inherit" passes the parent's console HANDLE to the child and any
grandchildren it spawns (doctor, gateway restart, etc.).
PowerShell/CMD will not return the prompt until every holder of those
handles exits, causing the terminal to hang indefinitely even though
`openclaw update` has finished and the new version is installed.

Fix: resolve the child's stdio mode through a new exported helper
`resolvePostCoreUpdateChildStdio` that returns "pipe" on Windows and
"inherit" everywhere else. When piped, the child's stdout/stderr are
relayed to the parent process so terminal output is preserved.

Adds two unit tests for the stdio resolution helper, one per platform
class (win32 vs non-Windows).

Fixes openclaw#78445.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@BradGroux BradGroux force-pushed the fix/openclaw-78445-windows-update-hang-20260506090900 branch from 75fdac6 to 321608e Compare May 8, 2026 06:43
@BradGroux
Copy link
Copy Markdown
Member

Refreshed again after main moved and the PR became conflicting.

  • Rebased onto current origin/main.
  • Re-ran focused verification:
    • pnpm vitest run src/cli/update-cli/update-command.test.ts (24 tests)
    • pnpm build
    • pnpm exec oxlint src/cli/update-cli/update-command.ts src/cli/update-cli/update-command.test.ts
  • Left the broader pnpm check blocker documented above; it was outside this PR's diff.

Latest prepared head: 321608e00ba118421ea65124f494458ed229defd. Fresh CI should run from there.

@BradGroux BradGroux merged commit 2d65908 into openclaw:main May 8, 2026
110 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Fixes openclaw#78445.

- Use piped stdio for the post-core update child on Windows so the child and descendants do not inherit the parent console handles.
- Relay child stdout/stderr back to the parent when piped so update output remains visible.
- Keep non-Windows behavior on inherited stdio.
- Add focused coverage for the stdio resolver.

Verification:
- `pnpm vitest run src/cli/update-cli/update-command.test.ts`
- `pnpm build`
- `pnpm exec oxlint src/cli/update-cli/update-command.ts src/cli/update-cli/update-command.test.ts`
- Full GitHub CI green at `321608e00ba118421ea65124f494458ed229defd`.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rogerdigital pushed a commit to rogerdigital/openclaw that referenced this pull request May 9, 2026
Fixes openclaw#78445.

- Use piped stdio for the post-core update child on Windows so the child and descendants do not inherit the parent console handles.
- Relay child stdout/stderr back to the parent when piped so update output remains visible.
- Keep non-Windows behavior on inherited stdio.
- Add focused coverage for the stdio resolver.

Verification:
- `pnpm vitest run src/cli/update-cli/update-command.test.ts`
- `pnpm build`
- `pnpm exec oxlint src/cli/update-cli/update-command.ts src/cli/update-cli/update-command.test.ts`
- Full GitHub CI green at `321608e00ba118421ea65124f494458ed229defd`.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes proof: supplied External PR includes structured after-fix real behavior proof. size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: [Windows] openclaw update hangs after "✓ Updating via package manager" completes

2 participants