Skip to content

fix: remove duplicate restart message on Windows (schtasks)#63651

Open
Chudwx wants to merge 3 commits intoopenclaw:mainfrom
Chudwx:fix/double-restart-message
Open

fix: remove duplicate restart message on Windows (schtasks)#63651
Chudwx wants to merge 3 commits intoopenclaw:mainfrom
Chudwx:fix/double-restart-message

Conversation

@Chudwx
Copy link
Copy Markdown

@Chudwx Chudwx commented Apr 9, 2026

Summary

Remove direct stdout.write() calls from restartScheduledTask() and restartStartupEntry() in src/daemon/schtasks.ts that caused "Restarted Scheduled Task" to print twice.

Problem

When running openclaw gateway restart on Windows with the Scheduled Task service manager, the output shows:

Restarted Scheduled Task: OpenClaw Gateway
Found stale gateway process(es): 7764.
Stopping stale process(es) and retrying restart...
Restarted Scheduled Task: OpenClaw Gateway
Timed out after 60s waiting for gateway port 18789 to become healthy.

Root Cause

Both restartScheduledTask() and restartStartupEntry() in src/daemon/schtasks.ts call stdout.write() directly before returning. The caller runServiceRestart() (in lifecycle-core.ts) also emits a structured message via emitScheduledRestart(). When postRestartCheck detects stale pids and retries the restart, the second call also triggers emitScheduledRestart(), resulting in duplicate messages.

Fix

Remove the direct stdout.write() calls from both service restart functions. All restart messaging is already handled centrally by emitScheduledRestart().

Changed files:

  • src/daemon/schtasks.ts: Remove 2 lines

Testing

  • Manual test on Windows with Scheduled Task: run openclaw gateway restart and verify only one "Restarted" message appears

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S labels Apr 9, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 9, 2026

Greptile Summary

This PR removes two direct stdout.write() restart messages from restartScheduledTask() and restartStartupEntry() in src/daemon/schtasks.ts, eliminating the duplicate "Restarted Scheduled Task" output on Windows. The fix is correct — the caller (runServiceRestart in lifecycle-core.ts) already handles messaging via emitScheduledRestart(). The PR also bundles two browser extension commits that add SSRF policy enforcement for CDP URLs at profile-creation time, with appropriate test coverage.

Confidence Score: 5/5

Safe to merge; the fix is minimal and correct, the only finding is a P2 style cleanup.

The core schtasks fix is a clean two-line removal with a clear root cause. The bundled browser SSRF commits are well-tested. The single finding (unused stdout parameter in the private restartStartupEntry function) is P2 style and does not affect behavior.

No files require special attention.

Vulnerabilities

No security concerns identified. The browser SSRF policy changes (assertCdpEndpointAllowed) improve security by blocking private-network CDP URLs when a strict policy is configured.

Comments Outside Diff (1)

  1. src/daemon/schtasks.ts, line 537-547 (link)

    P2 Unused stdout parameter

    After removing the stdout.write() call, the stdout parameter in restartStartupEntry is no longer referenced in the function body. Since this is a private (non-exported) function, the unused parameter could be dropped to avoid a potential Oxlint no-unused-vars warning.

    The two call sites in restartScheduledTask would also need their stdout argument removed accordingly.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/daemon/schtasks.ts
    Line: 537-547
    
    Comment:
    **Unused `stdout` parameter**
    
    After removing the `stdout.write()` call, the `stdout` parameter in `restartStartupEntry` is no longer referenced in the function body. Since this is a private (non-exported) function, the unused parameter could be dropped to avoid a potential Oxlint `no-unused-vars` warning.
    
    
    
    The two call sites in `restartScheduledTask` would also need their `stdout` argument removed accordingly.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/daemon/schtasks.ts
Line: 537-547

Comment:
**Unused `stdout` parameter**

After removing the `stdout.write()` call, the `stdout` parameter in `restartStartupEntry` is no longer referenced in the function body. Since this is a private (non-exported) function, the unused parameter could be dropped to avoid a potential Oxlint `no-unused-vars` warning.

```suggestion
async function restartStartupEntry(
  env: GatewayServiceEnv,
): Promise<GatewayServiceRestartResult> {
  const runtime = await resolveFallbackRuntime(env);
  if (typeof runtime.pid === "number" && runtime.pid > 0) {
    await terminateGatewayProcessTree(runtime.pid, 300);
  }
  launchFallbackTaskScript(resolveTaskScriptPath(env));
  return { outcome: "completed" };
}
```

The two call sites in `restartScheduledTask` would also need their `stdout` argument removed accordingly.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: remove duplicate restart messages i..." | Re-trigger Greptile

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

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/daemon/schtasks.ts
import { inspectPortUsage } from "../infra/ports.js";
import { getWindowsInstallRoots } from "../infra/windows-install-roots.js";
import { killProcessTree } from "../process/kill-tree.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fix broken import of normalizeLowercaseStringOrEmpty

src/daemon/schtasks.ts now imports normalizeLowercaseStringOrEmpty from ../shared/string-coerce.js, but that module does not exist in the repo (src/shared/string-coerce.ts/js is missing). This causes module resolution to fail at load time, and because src/daemon/service.ts statically imports ./schtasks.js, daemon lifecycle commands can crash with ERR_MODULE_NOT_FOUND before any platform-specific branching.

Useful? React with 👍 / 👎.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 27, 2026

Codex review: needs changes before merge.

What this changes:

The PR removes Windows schtasks/startup-fallback restart stdout writes, factors scheduled-task run helpers/lowercase normalization, and adds browser profile CDP SSRF validation behavior and tests.

Required change before merge:

A narrow replacement PR is suitable because the observable Windows duplicate-output bug, affected files, and validation path are clear, while the source PR is mixed with unrelated security-sensitive browser changes and should not be repaired in place.

Security review:

Security review needs attention: The PR bundles unrelated browser CDP SSRF policy changes with a Windows gateway output fix, so that part needs to be split or explicitly security-reviewed before merge.

Review findings:

  • [P2] Preserve one completed restart success line — src/daemon/schtasks.ts:805-806
Review details

Best possible solution:

Centralize Windows gateway restart success messaging so schtasks and startup-fallback completed restart flows print exactly one human success line after the final retry/check path, while keeping browser CDP SSRF policy work in a separate security-reviewed change.

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

Yes. Current main has deterministic source evidence: schtasks/startup-fallback restart paths write to stdout, and stale-PID post-restart recovery can invoke the same restart implementation again.

Is this the best way to solve the issue?

No. Removing the service-layer writes alone is not the best fix because completed restarts are not centrally printed today, and the browser SSRF changes are outside the Windows restart-output scope.

Full review comments:

  • [P2] Preserve one completed restart success line — src/daemon/schtasks.ts:805-806
    This path still returns completed, and runServiceRestart() only prints central human output for scheduled restarts or handled recovery messages. Removing the schtasks/login-item writes makes ordinary successful Windows restarts silent; move one final success message to the lifecycle flow instead of deleting all completed-restart output.
    Confidence: 0.88

Overall correctness: patch is incorrect
Overall confidence: 0.86

Security concerns:

  • [medium] Split CDP SSRF policy changes — extensions/browser/src/browser/cdp.helpers.ts:44
    The diff changes assertCdpEndpointAllowed() so trusted private-network mode with no hostname allowlist can return before hostname policy resolution, and it adds profile-creation CDP validation in the same branch as the schtasks output fix. That security boundary should be reviewed separately on its own threat model.
    Confidence: 0.84

Acceptance criteria:

  • pnpm test src/cli/daemon-cli/lifecycle-core.test.ts src/cli/daemon-cli/lifecycle.test.ts src/daemon/schtasks.test.ts src/daemon/schtasks.stop.test.ts src/daemon/schtasks.startup-fallback.test.ts
  • pnpm exec oxfmt --check --threads=1 src/daemon/schtasks.ts src/cli/daemon-cli/lifecycle.ts src/cli/daemon-cli/lifecycle-core.ts src/cli/daemon-cli/lifecycle-core.test.ts src/cli/daemon-cli/lifecycle.test.ts
  • pnpm check:changed in Testbox before handoff if runtime files change

What I checked:

  • Current main still writes scheduled-task restart output in the service layer: restartScheduledTask() calls runScheduledTaskOrThrow() and then writes Restarted Scheduled Task directly to stdout before returning { outcome: "completed" }. (src/daemon/schtasks.ts:1013, a7a8c8121a67)
  • Current main still writes startup-fallback restart output in the service layer: restartStartupEntry() writes Restarted Windows login item directly to stdout before returning a completed restart result. (src/daemon/schtasks.ts:596, a7a8c8121a67)
  • Stale-PID retry can call the restart implementation again: When post-restart health finds stale gateway PIDs, lifecycle.ts terminates them and calls service.restart({ env: process.env, stdout }) a second time, so service-layer success writes can repeat. (src/cli/daemon-cli/lifecycle.ts:264, a7a8c8121a67)
  • Completed restarts are not centrally printed today: runServiceRestart() only routes scheduled restart statuses through emitScheduledRestart(); after a completed restart it emits JSON state and prints only handled recovery messages, so deleting the schtasks writes would make normal completed restarts silent. (src/cli/daemon-cli/lifecycle-core.ts:523, a7a8c8121a67)
  • PR diff removes the scheduled-task success write without adding central completed output: The PR diff changes restartScheduledTask() from explicit /Run plus stdout success output to only await runScheduledTaskOrThrow(taskName); return { outcome: "completed" };. (src/daemon/schtasks.ts:805, e1b40c4701f8)
  • Current main has fallback-aware scheduled-task launch behavior that a replacement must preserve: runScheduledTaskOrThrow() now receives taskName, env, and scriptPath, and restartScheduledTask() passes those arguments so accepted-but-not-started scheduled tasks can relaunch through the fallback script. (src/daemon/schtasks.ts:1008, a7a8c8121a67)

Likely related people:

  • steipete: Current-main blame/history for the Windows schtasks restart flow, lifecycle restart handling, and browser CDP/profile files points to Peter Steinberger; this is the clearest available routing signal for both affected surfaces. (role: recent maintainer; confidence: medium; commits: 0bb52118e6c8, a7a8c8121a67; files: src/daemon/schtasks.ts, src/cli/daemon-cli/lifecycle.ts, src/cli/daemon-cli/lifecycle-core.ts)

Remaining risk / open question:

  • No Windows live smoke was run in this read-only review; the duplicate path is clear from source, but final validation should include targeted tests and preferably a Windows manual run.
  • The browser CDP SSRF changes overlap current-main behavior and should be split from this gateway fix rather than carried through an automated replacement.

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

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

Labels

gateway Gateway runtime size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants