Skip to content

fix(windows): startup safety falsely marks healthy scheduler service stale #432

Closed

Description

@0disoft

Client or integration

OpenCodex dashboard

Area

Service lifecycle

Summary

On Windows, a successfully installed and functioning Task Scheduler service is reported as stale by both ocx status and the Dashboard's Startup Safety page.

The generated task XML explicitly contains enabled/default lifecycle fields, but Windows canonicalizes the registered task on export and omits default-valued elements. windowsTaskRegistrationHealthy() currently requires those elements to be present literally, so a healthy task is rejected.

Expected: an enabled, logon-triggered scheduler task using the expected wscript.exe launcher is considered viable even when Windows omits XML elements whose effective values are the expected defaults.

Actual: Restart safety: AT RISK after restart (background service files are stale...) remains after a successful elevated reinstall.

Reproduction

  1. Install OpenCodex 2.7.39 on Windows 11.

  2. In an elevated PowerShell, run:

    ocx service install
    Start-Sleep 8
    ocx status
  3. Installation reports success:

    opencodex service installed + started (auto-starts on login, auto-restarts on crash).
    
  4. Status still reports:

    Restart safety: AT RISK after restart (background service files are stale; run 'ocx service install')
    Service: installed, stale or missing service assets
    
  5. The generated opencodex-service-task.xml contains:

    <LogonTrigger>
      <Enabled>true</Enabled>
    </LogonTrigger>
    <Principal id="Author">
      <LogonType>InteractiveToken</LogonType>
      <RunLevel>LeastPrivilege</RunLevel>
    </Principal>
    ...
    <Enabled>true</Enabled>
  6. The registered task exported by schtasks.exe /query /tn opencodex-proxy /xml is canonicalized to this equivalent form:

    <LogonTrigger />
    <Principal id="Author">
      <UserId>[REDACTED SID]</UserId>
      <LogonType>InteractiveToken</LogonType>
    </Principal>
    <Settings>
      ...
      <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
      <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
      ...
    </Settings>

    The default-valued Enabled=true and RunLevel=LeastPrivilege elements are omitted.

  7. Independent task/process checks show the installation is functional:

    • Task is enabled.
    • Trigger is at user logon.
    • Last task result is 0.
    • Expected wscript.exe /b /nologo ...opencodex-service-launcher.vbs action is registered.
    • service-state.json records backend scheduler; its baked Bun and CLI paths exist.
    • Proxy health is OK on port 10100.

Re-running ocx service install reproduces the same result because Task Scheduler exports the same canonicalized XML.

Version

2.7.39

Operating system

Microsoft Windows 11 Pro, version 10.0.26200, x64; PowerShell 7.6.3

Provider and model

Not provider-specific.

Logs or error output

$ ocx health --json
{"ok":true,"pid":[REDACTED],"port":10100}

$ ocx status
Proxy: running
Health: http://127.0.0.1:10100/healthz ok v2.7.39
Restart safety: AT RISK after restart (background service files are stale; run 'ocx service install')
Service: installed, stale or missing service assets

Suggested fix

In windowsTaskRegistrationHealthy(), interpret omitted default-valued fields according to Task Scheduler semantics:

  • Missing trigger Enabled => enabled; explicit false => unhealthy.
  • Missing settings Enabled => enabled; explicit false => unhealthy.
  • Missing RunLevel => LeastPrivilege; explicit non-LeastPrivilege => unhealthy.

Keep the existing exact checks for action path, launcher arguments, logon type, multiple-instance policy, and execution limit.

A regression test using the canonicalized exported XML above would catch this without weakening checks for explicit unsafe values.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. lidge-jun commented on Jul 25, 2026

    @lidge-jun
    Owner

    Fixed on dev in f0afdfce, included in integration SHA a5ec15e3.

    Task Scheduler XML now interprets omitted default-valued Enabled and RunLevel fields according to their effective Windows defaults, while still rejecting explicit unsafe values, duplicate/shadowed elements, namespace tricks, and decoy sections. The production diagnostic path and canonicalized XML regression are covered by tests.

    Validation: Cross-platform CI and the Windows windows-schtasks job in Service lifecycle both passed for the exact integration SHA.

  2. 0disoft commented on Jul 26, 2026

    @0disoft
    ContributorAuthor

    Thanks for landing the omitted-default fix. I installed the stable 2.7.40 release and re-ran the original reproduction. The default-value portion is fixed, but the same service still remains stale because Task Scheduler also canonicalizes the action arguments' quote entities.

    2.7.40 result

    opencodex 2.7.40
    
    Restart safety: AT RISK after restart (background service files are stale)
    Service: installed, stale or missing service assets
    

    The generated task file contains:

    <Arguments>/b /nologo &quot;C:\Users\[USER]\.opencodex\opencodex-service-launcher.vbs&quot;</Arguments>

    After registration, schtasks.exe /query /tn opencodex-proxy /xml exports the equivalent text-node value with literal quotes:

    <Arguments>/b /nologo "C:\Users\[USER]\.opencodex\opencodex-service-launcher.vbs"</Arguments>

    Directly evaluating the production diagnostic against that exported XML on 2.7.40 gives:

    {
      "healthy": false,
      "state": {
        "installed": true,
        "enabled": true,
        "registrationHealthy": false
      },
      "hasEntity": false,
      "hasLiteralArgs": true
    }

    The remaining mismatch appears to be this exact string check in windowsTaskRegistrationHealthy():

    action.includes(`<Arguments>${taskXmlString(`/b /nologo "${launcher}"`)}</Arguments>`)

    taskXmlString() produces &quot;, while the registered-task export has already decoded it to literal quotes. The command, launcher path, task enabled state, logon trigger, service-state paths, and live proxy are otherwise all valid.

    Could the action text be parsed/decoded before comparison (or safely accept both XML-equivalent encodings), with regression coverage for the literal-quote export shape? Re-running elevated ocx service install cannot change the canonicalized export, so it continues to reproduce on 2.7.40.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions