fix(windows): hide console window during gateway restart#54120
fix(windows): hide console window during gateway restart#54120zhiyuan1i wants to merge 3 commits intoopenclaw:mainfrom
Conversation
On Windows, the update-cli restart helper spawns a .bat script that polls for gateway port release using 'netstat | findstr'. Despite 'windowsHide: true' on the parent spawn(), the piped commands inside the batch script create a visible console window that persists until the port is released. Fix: On Windows, launch the batch script via a VBScript wrapper using WScript.Shell.Run with window style 0 (hidden). This guarantees no console window is ever visible to the user, regardless of what the batch script does internally. The VBScript self-deletes after launch. Also removes the now-unused quoteCmdScriptArg import from the restart-helper module. Fixes openclaw#39
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24f050ee72
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const child = spawn("wscript.exe", [vbsPath], { | ||
| detached: true, | ||
| stdio: "ignore", | ||
| windowsHide: true, | ||
| }); |
There was a problem hiding this comment.
Add cmd.exe fallback when wscript.exe is unavailable
This changes Windows restart execution to depend entirely on wscript.exe; in hardened Windows environments where Windows Script Host is disabled or blocked, spawn("wscript.exe", ...) fails and the update path no longer performs an automatic restart (it only falls back to a warning in maybeRestartService). Previously, direct cmd.exe execution still restarted the gateway, so this is a behavioral regression for those setups; consider catching this failure and falling back to the old cmd.exe /d /s /c ... path.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR fixes a visible console window during gateway restart on Windows by replacing direct Key issues found:
Confidence Score: 3/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/cli/update-cli/restart-helper.ts
Line: 174
Comment:
**Backslash doubling is wrong for VBScript**
VBScript strings do not use `\` as an escape character — backslashes are treated literally. The `.replace(/\\/g, "\\\\")` call writes `C:\\Users\\...\\script.bat` into the `.vbs` file instead of `C:\Users\...\script.bat`. While Windows path APIs often normalise double-backslashes, this is technically incorrect and **breaks for UNC paths** (e.g. `\\server\share\script.bat` becomes `\\\\server\\share\\script.bat`, which is a different UNC root).
Only the quote-doubling step is needed for VBScript string embedding:
```suggestion
const quotedBat = scriptPath.replace(/"/g, '""');
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/cli/update-cli/restart-helper.ts
Line: 177
Comment:
**Off-by-one in VBScript quote scheme produces a trailing stray `"` in the cmd.exe argument**
The template literal has **4 `"`** before `${quotedBat}` and **7 `"`** after it. After VBScript's `""` → `"` decoding the `Run` argument becomes:
```
cmd.exe /d /s /c ""<path>"""
```
With `cmd.exe /d /s /c`, the `/s` flag strips the **first and last** `"` of the argument string, yielding `"<path>""`. The extra trailing `"` is passed as an empty argument to the batch script. While in practice most batch scripts ignore unexpected arguments, this is still incorrect.
The correct pattern to have cmd.exe receive `"<path>"` (i.e., one `""` pair on each side in the VBScript string) needs **4 before + 5 after**:
```suggestion
`ws.Run "cmd.exe /d /s /c """"${quotedBat}""""", 0, False`,
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/cli/update-cli/restart-helper.ts
Line: 182-189
Comment:
**No error handling around VBScript file write**
Previously, `runRestartScript` only called `spawn` (synchronous, never throws). Adding `await fs.writeFile(...)` introduces a new failure mode: if the write fails (disk full, permissions), the function rejects and the gateway restart silently fails. Consider wrapping the Windows path in a try/catch to fall back gracefully, similar to how `prepareRestartScript` already does:
```typescript
try {
await fs.writeFile(vbsPath, vbsContent, "utf8");
} catch {
// Fall back: run the bat directly (console flash may be visible but restart succeeds)
const child = spawn("cmd.exe", ["/d", "/s", "/c", scriptPath], {
detached: true,
stdio: "ignore",
windowsHide: true,
});
child.unref();
return;
}
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(windows): hide console window during..." | Re-trigger Greptile |
src/cli/update-cli/restart-helper.ts
Outdated
| if (process.platform === "win32") { | ||
| // Write a VBScript wrapper that runs the batch script fully hidden. | ||
| const vbsPath = scriptPath.replace(/\.bat$/i, ".vbs"); | ||
| const quotedBat = scriptPath.replace(/\\/g, "\\\\").replace(/"/g, '""'); |
There was a problem hiding this comment.
Backslash doubling is wrong for VBScript
VBScript strings do not use \ as an escape character — backslashes are treated literally. The .replace(/\\/g, "\\\\") call writes C:\\Users\\...\\script.bat into the .vbs file instead of C:\Users\...\script.bat. While Windows path APIs often normalise double-backslashes, this is technically incorrect and breaks for UNC paths (e.g. \\server\share\script.bat becomes \\\\server\\share\\script.bat, which is a different UNC root).
Only the quote-doubling step is needed for VBScript string embedding:
| const quotedBat = scriptPath.replace(/\\/g, "\\\\").replace(/"/g, '""'); | |
| const quotedBat = scriptPath.replace(/"/g, '""'); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/update-cli/restart-helper.ts
Line: 174
Comment:
**Backslash doubling is wrong for VBScript**
VBScript strings do not use `\` as an escape character — backslashes are treated literally. The `.replace(/\\/g, "\\\\")` call writes `C:\\Users\\...\\script.bat` into the `.vbs` file instead of `C:\Users\...\script.bat`. While Windows path APIs often normalise double-backslashes, this is technically incorrect and **breaks for UNC paths** (e.g. `\\server\share\script.bat` becomes `\\\\server\\share\\script.bat`, which is a different UNC root).
Only the quote-doubling step is needed for VBScript string embedding:
```suggestion
const quotedBat = scriptPath.replace(/"/g, '""');
```
How can I resolve this? If you propose a fix, please make it concise.
src/cli/update-cli/restart-helper.ts
Outdated
| const quotedBat = scriptPath.replace(/\\/g, "\\\\").replace(/"/g, '""'); | ||
| const vbsContent = [ | ||
| 'Set ws = CreateObject("WScript.Shell")', | ||
| `ws.Run "cmd.exe /d /s /c """"${quotedBat}""""""", 0, False`, |
There was a problem hiding this comment.
Off-by-one in VBScript quote scheme produces a trailing stray
" in the cmd.exe argument
The template literal has 4 " before ${quotedBat} and 7 " after it. After VBScript's "" → " decoding the Run argument becomes:
cmd.exe /d /s /c ""<path>"""
With cmd.exe /d /s /c, the /s flag strips the first and last " of the argument string, yielding "<path>"". The extra trailing " is passed as an empty argument to the batch script. While in practice most batch scripts ignore unexpected arguments, this is still incorrect.
The correct pattern to have cmd.exe receive "<path>" (i.e., one "" pair on each side in the VBScript string) needs 4 before + 5 after:
| `ws.Run "cmd.exe /d /s /c """"${quotedBat}""""""", 0, False`, | |
| `ws.Run "cmd.exe /d /s /c """"${quotedBat}""""", 0, False`, |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/update-cli/restart-helper.ts
Line: 177
Comment:
**Off-by-one in VBScript quote scheme produces a trailing stray `"` in the cmd.exe argument**
The template literal has **4 `"`** before `${quotedBat}` and **7 `"`** after it. After VBScript's `""` → `"` decoding the `Run` argument becomes:
```
cmd.exe /d /s /c ""<path>"""
```
With `cmd.exe /d /s /c`, the `/s` flag strips the **first and last** `"` of the argument string, yielding `"<path>""`. The extra trailing `"` is passed as an empty argument to the batch script. While in practice most batch scripts ignore unexpected arguments, this is still incorrect.
The correct pattern to have cmd.exe receive `"<path>"` (i.e., one `""` pair on each side in the VBScript string) needs **4 before + 5 after**:
```suggestion
`ws.Run "cmd.exe /d /s /c """"${quotedBat}""""", 0, False`,
```
How can I resolve this? If you propose a fix, please make it concise.
src/cli/update-cli/restart-helper.ts
Outdated
| await fs.writeFile(vbsPath, vbsContent, "utf8"); | ||
| const child = spawn("wscript.exe", [vbsPath], { | ||
| detached: true, | ||
| stdio: "ignore", | ||
| windowsHide: true, | ||
| }); | ||
| child.unref(); | ||
| return; |
There was a problem hiding this comment.
No error handling around VBScript file write
Previously, runRestartScript only called spawn (synchronous, never throws). Adding await fs.writeFile(...) introduces a new failure mode: if the write fails (disk full, permissions), the function rejects and the gateway restart silently fails. Consider wrapping the Windows path in a try/catch to fall back gracefully, similar to how prepareRestartScript already does:
try {
await fs.writeFile(vbsPath, vbsContent, "utf8");
} catch {
// Fall back: run the bat directly (console flash may be visible but restart succeeds)
const child = spawn("cmd.exe", ["/d", "/s", "/c", scriptPath], {
detached: true,
stdio: "ignore",
windowsHide: true,
});
child.unref();
return;
}Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/update-cli/restart-helper.ts
Line: 182-189
Comment:
**No error handling around VBScript file write**
Previously, `runRestartScript` only called `spawn` (synchronous, never throws). Adding `await fs.writeFile(...)` introduces a new failure mode: if the write fails (disk full, permissions), the function rejects and the gateway restart silently fails. Consider wrapping the Windows path in a try/catch to fall back gracefully, similar to how `prepareRestartScript` already does:
```typescript
try {
await fs.writeFile(vbsPath, vbsContent, "utf8");
} catch {
// Fall back: run the bat directly (console flash may be visible but restart succeeds)
const child = spawn("cmd.exe", ["/d", "/s", "/c", scriptPath], {
detached: true,
stdio: "ignore",
windowsHide: true,
});
child.unref();
return;
}
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 538f310fe4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Write a VBScript wrapper that runs the batch script fully hidden. | ||
| // VBScript does not use \ as an escape character, so only quotes need doubling. | ||
| const vbsPath = scriptPath.replace(/\.bat$/i, ".vbs"); | ||
| const quotedBat = scriptPath.replace(/"/g, '""'); |
There was a problem hiding this comment.
Escape cmd expansion chars in Windows restart path
The new Windows wrapper only escapes double quotes (scriptPath.replace(/"/g, '""')) before embedding the batch path into cmd.exe /d /s /c, but cmd.exe still expands %VAR% inside quoted strings. If the temp/script path contains a literal % (valid on NTFS and possible via custom TEMP), the command line is rewritten and the .bat path can no longer be resolved, so auto-restart silently fails. The prior implementation used quoteCmdScriptArg to escape %/!; that escaping needs to be preserved for the VBS command string (and fallback cmd.exe path).
Useful? React with 👍 / 👎.
Summary
Fixes #39 — On Windows, the gateway restart script (generated during
openclaw update) shows a visible console window runningfindstr /R /C":18789 .*LISTEN"that persists until the port is released.Root Cause
runRestartScript()insrc/cli/update-cli/restart-helper.tsspawnscmd.exewithwindowsHide: true, but this flag doesn't prevent child processes within the batch script (specifically thenetstat -ano | findstrpipeline) from creating visible console windows.Fix
On Windows, instead of directly spawning
cmd.exe, we now:.vbs) alongside the.batfileWScript.Shell.RunwithintWindowStyle = 0(hidden) to launchcmd.exe /c script.batThis guarantees no console window is ever visible to the user, regardless of what commands the batch script runs internally. The
WScript.Shell.Run(..., 0, False)approach is the standard Windows way to run batch scripts silently.Changes
restart-helper.ts: RefactoredrunRestartScript()to use VBScript wrapper on Windowsrestart-helper.test.ts: Updated tests to match new Windows behavior (wscript.exe + VBS wrapper instead of direct cmd.exe)quoteCmdScriptArgimportTesting