Skip to content

fix(windows): hide console window during gateway restart#54120

Open
zhiyuan1i wants to merge 3 commits intoopenclaw:mainfrom
zhiyuan1i:fix/windows-restart-visible-console
Open

fix(windows): hide console window during gateway restart#54120
zhiyuan1i wants to merge 3 commits intoopenclaw:mainfrom
zhiyuan1i:fix/windows-restart-visible-console

Conversation

@zhiyuan1i
Copy link
Copy Markdown

Summary

Fixes #39 — On Windows, the gateway restart script (generated during openclaw update) shows a visible console window running findstr /R /C":18789 .*LISTEN" that persists until the port is released.

Root Cause

runRestartScript() in src/cli/update-cli/restart-helper.ts spawns cmd.exe with windowsHide: true, but this flag doesn't prevent child processes within the batch script (specifically the netstat -ano | findstr pipeline) from creating visible console windows.

Fix

On Windows, instead of directly spawning cmd.exe, we now:

  1. Write a small VBScript wrapper (.vbs) alongside the .bat file
  2. The VBScript uses WScript.Shell.Run with intWindowStyle = 0 (hidden) to launch cmd.exe /c script.bat
  3. The VBScript self-deletes after launch

This 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: Refactored runRestartScript() to use VBScript wrapper on Windows
  • restart-helper.test.ts: Updated tests to match new Windows behavior (wscript.exe + VBS wrapper instead of direct cmd.exe)
  • Removed unused quoteCmdScriptArg import

Testing

  • Verified the fix locally on Windows 11 (10.0.26200)
  • The restart script now executes completely hidden with no visible console window

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
@openclaw-barnacle openclaw-barnacle bot added cli CLI command changes size: S labels Mar 25, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +183 to +187
const child = spawn("wscript.exe", [vbsPath], {
detached: true,
stdio: "ignore",
windowsHide: true,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 25, 2026

Greptile Summary

This PR fixes a visible console window during gateway restart on Windows by replacing direct cmd.exe spawning with a VBScript wrapper that uses WScript.Shell.Run with intWindowStyle = 0. The approach is sound — windowsHide: true on a cmd.exe spawn does not suppress windows created by child processes, and the VBScript technique is the standard Windows workaround for this.

Key issues found:

  • Incorrect backslash escaping (restart-helper.ts:174): VBScript strings do not treat \ as an escape character, so .replace(/\\/g, "\\\\") produces double backslashes in the generated .vbs file. This works for typical local temp paths due to Windows path normalization, but is semantically wrong and will break for UNC paths (e.g. \\server\share\...).
  • Off-by-one in VBScript quote scheme (restart-helper.ts:177): The template uses 4 quotes before and 7 quotes after the path. After VBScript's "" decoding this gives cmd.exe the argument ""path""", and with /s /c stripping the outer pair, cmd.exe sees "path"" — a stray trailing ". For typical batch scripts without positional parameter usage this is harmless, but it is technically incorrect.
  • No error handling around the new fs.writeFile call (restart-helper.ts:182–189): Previously runRestartScript could not throw. The new VBScript file write adds a failure mode; if the write fails, the entire restart silently fails with an unhandled rejection.
  • The test suite drops the metacharacter quoting test without adding equivalent coverage for paths containing spaces or special characters via the VBScript path.

Confidence Score: 3/5

  • The overall fix direction is correct, but there are two concrete quoting bugs and a new unhandled failure mode that should be resolved before merging.
  • The VBScript approach is the right solution for hiding child-process console windows on Windows. However, the backslash doubling is wrong for VBScript (breaks UNC paths), the "" quote count is off by one pair (trailing stray " in the cmd.exe argument), and the new async file write introduces an unhandled failure mode that didn't exist before. These are concrete, fixable bugs rather than style nits, warranting a 3.
  • src/cli/update-cli/restart-helper.ts — specifically the quotedBat escaping and the quote scheme in the ws.Run line.
Prompt To Fix All 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.

---

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

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, '""');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 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:

Suggested change
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.

const quotedBat = scriptPath.replace(/\\/g, "\\\\").replace(/"/g, '""');
const vbsContent = [
'Set ws = CreateObject("WScript.Shell")',
`ws.Run "cmd.exe /d /s /c """"${quotedBat}""""""", 0, False`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 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:

Suggested change
`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.

Comment on lines +182 to +189
await fs.writeFile(vbsPath, vbsContent, "utf8");
const child = spawn("wscript.exe", [vbsPath], {
detached: true,
stdio: "ignore",
windowsHide: true,
});
child.unref();
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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, '""');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant