Skip to content

fix(daemon): query Windows task runtime directly#51486

Open
wangji0923 wants to merge 1 commit intoopenclaw:mainfrom
wangji0923:fix/schtasks-status-query-windows
Open

fix(daemon): query Windows task runtime directly#51486
wangji0923 wants to merge 1 commit intoopenclaw:mainfrom
wangji0923:fix/schtasks-status-query-windows

Conversation

@wangji0923
Copy link
Copy Markdown

Summary

  • remove the broad schtasks /Query preflight from readScheduledTaskRuntime() so Windows status reads go straight to the task-scoped /Query /TN ... /V /FO LIST lookup
  • keep the existing Startup-folder fallback behavior when the task-scoped lookup fails
  • add a regression test that locks in the task-scoped runtime query without the global preflight, and update the fallback fixture sequencing accordingly

Linked Issue

Repro + Verification

  • corepack pnpm test -- src/daemon/schtasks.test.ts src/daemon/schtasks.startup-fallback.test.ts src/daemon/inspect.test.ts
  • corepack pnpm exec oxfmt --check src/daemon/schtasks.ts src/daemon/schtasks.startup-fallback.test.ts

Human Verification

Review Conversations

  • None yet.

Risks and Mitigations

  • This changes only the Windows runtime-status read path; install / stop / restart still use the existing assertSchtasksAvailable() preflight.
  • The main behavior change is intentionally narrow: status no longer fails early on the broader global schtasks /Query path when the task-scoped query is the only data this code path needs.
  • I also attempted wider validation, but pnpm check did not finish within the local timeout window, and node scripts/tsdown-build.mjs hit a Rolldown panic in this environment rather than a repo-specific TypeScript/build error.

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: 69a6a226d2

ℹ️ 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 Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 21, 2026

Greptile Summary

This PR fixes #49187 by removing the unnecessary global schtasks /Query preflight from readScheduledTaskRuntime() in src/daemon/schtasks.ts. The function only ever needed the task-scoped query result, so the broad preflight was an extra round-trip that could fail independently of whether the task-scoped query would succeed, causing false-negative status reads.

Key changes:

  • readScheduledTaskRuntime now goes directly to schtasks /Query /TN <taskName> /V /FO LIST — the startup-folder fallback is still triggered when that task-scoped query returns non-zero.
  • addStartupFallbackMissingResponses in the test helper gains an { includePreflight } opt-in so callers that exercise code paths still using assertSchtasksAvailable() (e.g., restartScheduledTask) can push the extra fixture response without affecting tests that should not see a preflight.
  • A new regression test ("reads runtime from the task-scoped schtasks query without a global preflight") locks in the single-call behavior and asserts on the exact argument array.

One minor robustness note: the removed try-catch also silently handled the edge case where schtasks.exe is not found at all (ENOENT spawn error), falling back to the startup-entry runtime or returning { status: "unknown" }. With the preflight gone, that spawn-level exception now propagates uncaught from readScheduledTaskRuntime. On a real Windows system schtasks.exe is always present, so this is an extreme edge case, but it is a subtle behavior change from the previous code.

Confidence Score: 5/5

  • Safe to merge — the change is narrowly scoped, the logic is correct, and the test coverage directly validates the intended behavior.
  • The core change (removing the redundant preflight) is correct and well-targeted. The test suite is updated consistently: the new test locks in the single task-scoped call, the refactored helper preserves fixture correctness for functions that still use assertSchtasksAvailable(), and the import of schtasksCalls was already exported. The only open item is the P2 robustness note about uncaught spawn errors in the ENOENT edge case, which is an extreme corner case on Windows and does not affect normal operation.
  • No files require special attention; the P2 comment on src/daemon/schtasks.ts is a robustness suggestion, not a blocker.

Comments Outside Diff (1)

  1. src/daemon/schtasks.ts, line 748-761 (link)

    P2 Unhandled spawn errors no longer caught

    The removed try-catch block also handled the case where execSchtasks itself throws — specifically when the schtasks.exe binary is missing and Node's spawn emits an error event (ENOENT). Looking at runCommandWithTimeout, the child.on("error", ...) handler calls reject(err), which propagates through execSchtasks as a thrown exception.

    In the old code this was caught by the try-catch around assertSchtasksAvailable(), and the function would either fall back to the startup-entry runtime or return { status: "unknown", detail: String(err) }. With the preflight removed, a spawn error now propagates uncaught out of readScheduledTaskRuntime.

    In practice schtasks.exe is always present on Windows, so this is an extreme edge case. But if defensive coverage matters here (consistent with the pattern in stopScheduledTask and restartScheduledTask), you could wrap the execSchtasks call:

      const res = await execSchtasks(["/Query", "/TN", taskName, "/V", "/FO", "LIST"]).catch(
        (err: unknown): { code: number; stdout: string; stderr: string } => ({
          code: 1,
          stdout: "",
          stderr: String(err),
        }),
      );
    

    This keeps the rest of the non-zero-code handling intact without reintroducing the preflight.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/daemon/schtasks.ts
    Line: 748-761
    
    Comment:
    **Unhandled spawn errors no longer caught**
    
    The removed `try-catch` block also handled the case where `execSchtasks` itself throws — specifically when the `schtasks.exe` binary is missing and Node's `spawn` emits an `error` event (ENOENT). Looking at `runCommandWithTimeout`, the `child.on("error", ...)` handler calls `reject(err)`, which propagates through `execSchtasks` as a thrown exception.
    
    In the old code this was caught by the `try-catch` around `assertSchtasksAvailable()`, and the function would either fall back to the startup-entry runtime or return `{ status: "unknown", detail: String(err) }`. With the preflight removed, a spawn error now propagates uncaught out of `readScheduledTaskRuntime`.
    
    In practice `schtasks.exe` is always present on Windows, so this is an extreme edge case. But if defensive coverage matters here (consistent with the pattern in `stopScheduledTask` and `restartScheduledTask`), you could wrap the `execSchtasks` call:
    
    ```
      const res = await execSchtasks(["/Query", "/TN", taskName, "/V", "/FO", "LIST"]).catch(
        (err: unknown): { code: number; stdout: string; stderr: string } => ({
          code: 1,
          stdout: "",
          stderr: String(err),
        }),
      );
    ```
    
    This keeps the rest of the non-zero-code handling intact without reintroducing the preflight.
    
    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: 748-761

Comment:
**Unhandled spawn errors no longer caught**

The removed `try-catch` block also handled the case where `execSchtasks` itself throws — specifically when the `schtasks.exe` binary is missing and Node's `spawn` emits an `error` event (ENOENT). Looking at `runCommandWithTimeout`, the `child.on("error", ...)` handler calls `reject(err)`, which propagates through `execSchtasks` as a thrown exception.

In the old code this was caught by the `try-catch` around `assertSchtasksAvailable()`, and the function would either fall back to the startup-entry runtime or return `{ status: "unknown", detail: String(err) }`. With the preflight removed, a spawn error now propagates uncaught out of `readScheduledTaskRuntime`.

In practice `schtasks.exe` is always present on Windows, so this is an extreme edge case. But if defensive coverage matters here (consistent with the pattern in `stopScheduledTask` and `restartScheduledTask`), you could wrap the `execSchtasks` call:

```
  const res = await execSchtasks(["/Query", "/TN", taskName, "/V", "/FO", "LIST"]).catch(
    (err: unknown): { code: number; stdout: string; stderr: string } => ({
      code: 1,
      stdout: "",
      stderr: String(err),
    }),
  );
```

This keeps the rest of the non-zero-code handling intact without reintroducing the preflight.

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

Last reviewed commit: "Daemon: query Window..."

@wangji0923 wangji0923 force-pushed the fix/schtasks-status-query-windows branch from f62277c to 87009a1 Compare March 26, 2026 03:48
@wangji0923
Copy link
Copy Markdown
Author

Refreshed this PR onto the current upstream/main.

The runtime path still needs the original fix on main: readScheduledTaskRuntime() was still doing a global schtasks /Query preflight before the task-scoped query, so this refresh keeps the scope narrow:

  • query schtasks /Query /TN <taskName> /V /FO LIST directly for runtime reads
  • preserve the Startup fallback behavior when that task-scoped query fails or the execSchtasks call itself throws
  • lock in the single-call behavior plus the spawn-error fallback with focused regression coverage

Re-verified with focused checks:

  • pnpm exec oxfmt --check src/daemon/schtasks.ts src/daemon/schtasks.startup-fallback.test.ts src/daemon/test-helpers/schtasks-base-mocks.ts src/daemon/test-helpers/schtasks-fixtures.ts
  • pnpm exec oxlint src/daemon/schtasks.ts src/daemon/schtasks.startup-fallback.test.ts src/daemon/test-helpers/schtasks-base-mocks.ts src/daemon/test-helpers/schtasks-fixtures.ts
  • pnpm test -- src/daemon/schtasks.startup-fallback.test.ts

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 28, 2026

Codex review: needs real behavior proof before merge.

Summary
The PR removes the global Windows schtasks /Query preflight from readScheduledTaskRuntime(), catches task-scoped query throws for fallback handling, updates schtasks fixtures/tests, and adds a changelog entry.

Reproducibility: yes. Source inspection of current main shows a high-confidence mocked reproduction path: make broad schtasks /Query fail while /Query /TN "OpenClaw Gateway" /V /FO LIST would succeed or a Startup fallback exists; I did not run a live Windows scheduled task.

Real behavior proof
Needs real behavior proof before merge: Only tests, lint/format output, and CI are provided; the PR body explicitly says live Windows scheduled-task verification was not run, and the exact-head real-behavior-proof check is failing. 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
Human follow-up is needed to provide or approve real Windows behavior proof and wait for remaining exact-head CI; there is no narrow code defect left for an automated repair.

Security
Cleared: The diff is limited to Windows daemon status handling, Vitest fixtures, and changelog text, with no dependency, CI, install, permission, secret, or publishing changes.

Review details

Best possible solution:

Land the narrow patch after exact-head CI is green and someone adds redacted live Windows openclaw status or scheduled-task proof showing the named-task query path succeeds or falls back as intended.

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

Yes. Source inspection of current main shows a high-confidence mocked reproduction path: make broad schtasks /Query fail while /Query /TN "OpenClaw Gateway" /V /FO LIST would succeed or a Startup fallback exists; I did not run a live Windows scheduled task.

Is this the best way to solve the issue?

Yes for the code direction. Removing only the runtime-status preflight while preserving task-scoped failure fallback is the narrow maintainable fix, but merge readiness still depends on real behavior proof and final CI.

What I checked:

  • Current main still preflights runtime status: On current main, readScheduledTaskRuntime() calls assertSchtasksAvailable() before the named /Query /TN ... /V /FO LIST query, so a broad /Query failure can stop status before the data-bearing lookup. (src/daemon/schtasks.ts:1028, cf0f1a171a04)
  • Windows status path uses the runtime reader: The win32 gateway service adapter wires readRuntime to readScheduledTaskRuntime, so the change affects Windows gateway status reads. (src/daemon/service.ts:277, cf0f1a171a04)
  • PR head implements the direct task query: The PR head queries /Query /TN <taskName> /V /FO LIST directly and maps thrown execSchtasks errors into the existing non-zero/fallback handling. (src/daemon/schtasks.ts:1028, b80ae7a0dabf)
  • Regression coverage locks the intended behavior: The refreshed tests assert a single task-scoped schtasks call with no global preflight and cover thrown-query fallback to Startup runtime. (src/daemon/schtasks.startup-fallback.test.ts:401, b80ae7a0dabf)
  • Changelog entry present: The PR adds a user-facing Gateway/Windows changelog entry for the status fix and credits the external contributor. (CHANGELOG.md:67, b80ae7a0dabf)
  • Real behavior proof is still missing: The PR body says live Windows scheduled-task verification was not run; the maintainer refresh comment lists format, oxlint, and Vitest checks only; GitHub currently reports the Real behavior proof check as failing. (b80ae7a0dabf)

Likely related people:

  • steipete: Recent current-main history repeatedly touches Windows schtasks lifecycle, startup fallback, and daemon service flow around this runtime status path. (role: recent maintainer and likely follow-up owner; confidence: high; commits: 13f9deb61979, fccb2b8ace6c, 0fb9a3beacc2; files: src/daemon/schtasks.ts, src/daemon/schtasks.startup-fallback.test.ts, src/daemon/service.ts)
  • tmimmanuel: Authored the merged Windows scheduled-task restart/install behavior work and related startup-fallback test updates near this code path. (role: adjacent feature-history owner; confidence: medium; commits: 0fef95b17d72; files: src/daemon/schtasks.ts, src/daemon/schtasks.startup-fallback.test.ts)
  • vincentkoc: Recently touched src/daemon/service.ts, whose win32 adapter routes gateway status into readScheduledTaskRuntime. (role: recent gateway service maintainer; confidence: medium; commits: 97d35f4c575b; files: src/daemon/service.ts)
  • BradGroux: Force-pushed the current prepared head and documented the local verification now waiting on CI/proof. (role: maintainer refresh owner; confidence: high; commits: b80ae7a0dabf; files: src/daemon/schtasks.ts, src/daemon/schtasks.startup-fallback.test.ts, CHANGELOG.md)

Remaining risk / open question:

  • External after-fix proof from a real Windows scheduled-task setup is still missing; the PR body explicitly says live verification was not run and the proof check is failing.
  • One exact-head CI check was still in progress at review time.

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

@BradGroux BradGroux force-pushed the fix/schtasks-status-query-windows branch from 87009a1 to b80ae7a Compare May 9, 2026 09:27
@BradGroux
Copy link
Copy Markdown
Member

Maintainer refresh: ported this onto current origin/main, preserved the direct task-scoped runtime query behavior, kept the thrown-query Startup fallback coverage, and added the changelog entry.

Prepared head: b80ae7a0dabff8aac5ece86965fdff6c51d904be

Local verification passed:

  • pnpm exec oxfmt --check --threads=1 src/daemon/schtasks.ts src/daemon/schtasks.startup-fallback.test.ts src/daemon/test-helpers/schtasks-base-mocks.ts src/daemon/test-helpers/schtasks-fixtures.ts CHANGELOG.md
  • pnpm exec oxlint src/daemon/schtasks.ts src/daemon/schtasks.startup-fallback.test.ts src/daemon/test-helpers/schtasks-base-mocks.ts src/daemon/test-helpers/schtasks-fixtures.ts
  • pnpm vitest run src/daemon/schtasks.test.ts src/daemon/schtasks.startup-fallback.test.ts src/daemon/inspect.test.ts

Waiting on fresh GitHub CI before merge.

@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Bug: schtasks unavailable error on Windows

2 participants