Skip to content

fix(daemon): accept localized schtasks running states#41034

Open
Narcooo wants to merge 4 commits intoopenclaw:mainfrom
Narcooo:refresh/39451-schtasks-localized-running
Open

fix(daemon): accept localized schtasks running states#41034
Narcooo wants to merge 4 commits intoopenclaw:mainfrom
Narcooo:refresh/39451-schtasks-localized-running

Conversation

@Narcooo
Copy link
Copy Markdown
Contributor

@Narcooo Narcooo commented Mar 9, 2026

Summary

  • normalize Scheduled Task status text before deriving runtime state
  • treat localized and mojibake variants of running states as running
  • keep the existing stale-running safeguard based on Last Run Result

Problem

openclaw node status only recognized the English Running schtasks state, so localized Windows hosts like German Wird ausgeführt were incorrectly reported as stopped.

Verification

  • pnpm exec vitest run src/daemon/schtasks.test.ts
  • pnpm exec oxfmt --check src/daemon/schtasks.ts src/daemon/schtasks.test.ts
  • pnpm exec oxlint src/daemon/schtasks.ts src/daemon/schtasks.test.ts

Fixes #39057

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 9, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Locale-dependent Scheduled Task status misclassified as STOPPED, causing unsafe process termination during restart health checks

1. 🟡 Locale-dependent Scheduled Task status misclassified as STOPPED, causing unsafe process termination during restart health checks

Property Value
Severity Medium
CWE CWE-754
Location src/daemon/schtasks.ts:170-190

Description

The Windows scheduled-task runtime derivation now defaults to stopped whenever the task status string is non-empty but not recognized as a running status.

  • isRunningTaskStatus() only recognizes:
    • English "Running"
    • German "Wird ausgeführt" (via startsWith("wird ausgef") after normalization)
  • For other locales where the schtasks key is still Status but the value is localized (e.g., Dutch, etc.), a running task may be reported as stopped when Last Run Result is missing/unparseable.
  • Downstream, restart health checks on Windows treat runtime.status !== "running" as a signal that any OpenClaw listener PIDs are stale and should be killed. This can terminate a legitimately running gateway process, creating an availability/DoS risk during update/restart flows.

Vulnerable logic:

if (!isRunningTaskStatus(parsed.status)) {
  return { status: "stopped" };
}

Data-flow / impact chain:

  • Source: schtasks /Query /V /FO LIST output → parsed into ScheduledTaskInfo.status
  • Decision: deriveScheduledTaskRuntimeStatus() classifies runtime as stopped
  • Sink: restart-health logic may include gateway listener PIDs as stale when status !== "running", and callers (restart/update flows) can terminate those PIDs.

Recommendation

Treat unrecognized localized Status values as unknown unless a reliable numeric Last Run Result indicates running/stopped.

Suggested change (safer default):

const normalizedResult = normalizeTaskResultCode(parsed.lastRunResult);
if (normalizedResult && RUNNING_RESULT_CODES.has(normalizedResult)) {
  return { status: "running" };
}// If the status is not confidently recognized, don't assume stopped.
if (!isRunningTaskStatus(parsed.status)) {
  return {
    status: "unknown",
    detail: "Unrecognized Scheduled Task status string; locale-dependent and no numeric Last Run Result available.",
  };
}// Status looks like running; only treat as stopped if we have a non-running numeric result.
if (normalizedResult) {
  return {
    status: "stopped",
    detail: `Task Last Run Result=${parsed.lastRunResult}; treating as not running.`,
  };
}

return { status: "running" };

Additionally:

  • Consider expanding isRunningTaskStatus() to recognize more locales only as a best-effort enhancement, but keep the default as unknown to avoid unsafe remediation.
  • Alternatively, change restart-health logic on Windows to confirm reachability/port ownership even when runtime status is unknown/not-running, rather than immediately treating listener PIDs as stale.

Analyzed PR: #41034 at commit adb53b4

Last updated on: 2026-03-09T13:33:32Z

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

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR fixes openclaw node status incorrectly reporting stopped for tasks running on localized (non-English) Windows hosts by adding Unicode-aware status normalization and explicit recognition of the German running state "Wird ausgeführt" (including its mojibake form). The tui-event-handlers changes are cosmetic import reordering only.

Key changes:

  • normalizeTaskStatus applies NFKD decomposition and diacritic stripping so that accented characters collapse to their ASCII base forms before comparison.
  • isRunningTaskStatus recognises both "running" (English) and the "wird ausgef…" prefix (German/mojibake).
  • deriveScheduledTaskRuntimeStatus is restructured: the result code is checked first (authoritative), then the status text is consulted.

Issues found:

  • The test description on line 113 of schtasks.test.ts still reads "treats localized status without result code as unknown" but the expectation was updated to { status: "running" } — a stale name that actively misleads future readers.
  • Any non-English, non-German running status text (e.g. French "En cours d'exécution") that arrives without a 0x41301 result code will now return { status: "stopped" } instead of the previous { status: "unknown" }. This is a regression for those locales in edge cases where the result code has not been populated yet (e.g. very early in task startup).

Confidence Score: 3/5

  • Safe to merge for English and German locales; introduces a regression for other localized running states that lack a result code.
  • The core German fix is correct and well-tested. However, the new control flow silently classifies any unrecognized localized running status as stopped (was unknown), which is a more harmful false-negative for other locales. The stale test description is a minor quality issue.
  • src/daemon/schtasks.ts — review the isRunningTaskStatus / deriveScheduledTaskRuntimeStatus interaction for non-German localized running states without a result code.

Comments Outside Diff (1)

  1. src/daemon/schtasks.test.ts, line 113-119 (link)

    Stale test description

    The test description still reads "treats localized status without result code as unknown" but the expectation was updated to { status: "running" }. This is misleading for anyone reading the test suite — the name contradicts what is actually being asserted.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/daemon/schtasks.test.ts
    Line: 113-119
    
    Comment:
    **Stale test description**
    
    The test description still reads `"treats localized status without result code as unknown"` but the expectation was updated to `{ status: "running" }`. This is misleading for anyone reading the test suite — the name contradicts what is actually being asserted.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: adb53b4

Comment on lines +170 to +173
function isRunningTaskStatus(value?: string): boolean {
const normalized = normalizeTaskStatus(value);
return normalized === "running" || normalized.startsWith("wird ausgef");
}
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.

Non-German localized running states fall back to stopped

isRunningTaskStatus only recognises English ("running") and German ("wird ausgef…"). Any other locale's running status text — e.g. French "En cours d'exécution", Spanish "En ejecución", Japanese "実行中" — that arrives without a lastRunResult of 0x41301 (e.g. very early in startup before the result code is populated) will now reach the !isRunningTaskStatus branch and return { status: "stopped" }.

Before this PR those cases returned { status: "unknown" }, which at least did not lie to the caller. Silently flipping to "stopped" is a regression for those locales.

If the intent is to keep the old "unknown" fall-through for unrecognised running states rather than reporting "stopped", the guard in step 3 should be narrowed so it only fires for statuses that are positively known to be non-running (e.g. "ready", "disabled", etc.), or the function should return "unknown" when a non-empty status is present but unrecognised:

// Possible alternative for unrecognised status text with no result code
if (!isRunningTaskStatus(parsed.status)) {
  // A non-empty status we don't recognise could still be a localized
  // running state; prefer "unknown" over a false "stopped".
  return normalizedResult ? { status: "stopped" } : { status: "unknown" };
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/daemon/schtasks.ts
Line: 170-173

Comment:
**Non-German localized running states fall back to `stopped`**

`isRunningTaskStatus` only recognises English (`"running"`) and German (`"wird ausgef…"`). Any other locale's running status text — e.g. French `"En cours d'exécution"`, Spanish `"En ejecución"`, Japanese `"実行中"` — that arrives **without** a `lastRunResult` of `0x41301` (e.g. very early in startup before the result code is populated) will now reach the `!isRunningTaskStatus` branch and return `{ status: "stopped" }`.

Before this PR those cases returned `{ status: "unknown" }`, which at least did not lie to the caller. Silently flipping to `"stopped"` is a regression for those locales.

If the intent is to keep the old `"unknown"` fall-through for unrecognised running states rather than reporting `"stopped"`, the guard in step 3 should be narrowed so it only fires for statuses that are positively known to be non-running (e.g. `"ready"`, `"disabled"`, etc.), or the function should return `"unknown"` when a non-empty status is present but unrecognised:

```ts
// Possible alternative for unrecognised status text with no result code
if (!isRunningTaskStatus(parsed.status)) {
  // A non-empty status we don't recognise could still be a localized
  // running state; prefer "unknown" over a false "stopped".
  return normalizedResult ? { status: "stopped" } : { status: "unknown" };
}
```

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

ℹ️ 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 on lines +188 to +189
if (!isRunningTaskStatus(parsed.status)) {
return { status: "stopped" };
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 Return unknown for unrecognized localized running states

This branch now classifies any non-empty status text that is neither English Running nor German Wird ausgef… as stopped when Last Run Result is missing/non-numeric, which regresses the previous behavior of reporting unknown. On localized Windows hosts where running text differs (for example French) and no parseable result code is present, an actually running task will be reported as stopped, which can trigger incorrect status output and control decisions from deriveScheduledTaskRuntimeStatus.

Useful? React with 👍 / 👎.

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]: openclaw node status reports "stopped" on German Windows while task is running

1 participant