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

@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

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.

[Bug]: Bug: schtasks unavailable error on Windows

1 participant