Skip to content

Windows (non-English locales): fresh ocx service install never triggers the UAC elevated fallback — zh-CN "拒绝访问" access-denied is misclassified because the locale-independent fallback is gated behind /f #4182

Closed as not planned

Description

@dddaniel1

Client or integration

OpenCodex dashboard

Area

CLI

Summary

Environment

  • opencodex 2.49.0 (npm global, run via ocx.mjs → bun)
  • Windows 11 Home China (10.0.26200), display language zh-CN, code page 936/GBK
  • AV: Lenovo Anti-Virus powered by Huorong — blocks schtasks.exe /create with
    错误: 拒绝访问。 (GBK; "ERROR: Access is denied.") even for a LeastPrivilege/InteractiveToken
    task created by the user's own account. The Task Scheduler COM API is not blocked.

Observed behavior

❌ Service install cleanup failed: Command failed: C:\WINDOWS\system32\schtasks.exe
   /create /tn opencodex-proxy /xml C:\Users\...\Temp\opencodex-service-stage-XXXX\task.xml
   (mojibake — GBK bytes of 拒绝访问 decoded as UTF-8)

No UAC prompt ever appears. Install fails and rolls back, even though the designed
remediation for exactly this case — escalate and call Register-ScheduledTask (which
works fine under Huorong) — exists and would have succeeded.

Root cause (three gaps compound)

  1. Fresh-install create omits /f — buildWindowsSchtasksCreateArgsForXml(xml, replace=false)
    returns ["/create", "/tn", "opencodex-proxy", "/xml", xml] (src/service.ts:1843-1845).
  2. The locale-independent fallback is gated behind /f — isOwnedSchedulerCreate()
    requires normalized.includes("/f") (src/lib/windows-elevation.ts:338-348), so the
    reliable classifier
    schedulerExitStatus(error) === 1 && isCurrentWindowsProcessElevated() === false
    is unreachable for every fresh install.
  3. Text matching only knows English and German — windowsAccessDeniedText()
    (src/lib/windows-elevation.ts:280-286) checks access denied / zugriff verweigert.
    zh-CN emits GBK 拒绝访问, which additionally arrives mojibaked because runFile()
    captures stderr as a Buffer and the classifier stringifies it as UTF-8. (Same class of
    gap as Windows (zh-CN): ocx sync always refuses — localized schtasks output breaks the task-absence probe, and the 2s full-listing fallback times out #2914, but on the /create classification path instead of the /query probe.
    French/Spanish/etc. fail text matching too.)

Net effect: the error is classified "other", the catch in
registerFreshWindowsSchedulerTask (src/service.ts:2616-2624) rethrows, and elevation
never runs. The AV context above just makes the denial deterministic; any non-English
locale hits the classification bug the moment schtasks denies a create for any reason.

Suggested fix

Minimal (verified working locally, install succeeds afterwards):

--- a/src/lib/windows-elevation.ts
+++ b/src/lib/windows-elevation.ts
@@ function isOwnedSchedulerCreate(args: string[]): boolean {
   return normalized[0] === "/create"
     && normalized[taskIndex + 1] === "opencodex-proxy"
     && taskIndex > 0
     && xmlIndex > 0
-    && Boolean(args[xmlIndex + 1])
-    && normalized.includes("/f");
+    && Boolean(args[xmlIndex + 1]);

The /tn opencodex-proxy /xml shape is already specific enough that unrelated schtasks
failures cannot request elevation; requiring /f only excludes the fresh-install path
the fallback exists to serve.

A fuller fix would also decode schtasks stderr with legacyEncodingForLocale
(zh → gbk, per #2914's analysis) and extend windowsAccessDeniedText, but the
exit-status fallback is the only route that is robust across all locales.

Reproduction

  1. zh-CN Windows (cp936), non-elevated shell.
  2. Make schtasks /create fail with access denied (e.g. Huorong-family AV intercepting
    schtasks.exe; a standard-user restriction also works).
  3. ocx service install.
  4. Observe: no UAC prompt; raw mojibake failure; task rolled back.

Expected: access-denied classified via the exit-status route → UAC prompt → elevated
Register-ScheduledTask registers the task.

Version

opencodex 2.49.0

Operating system

Windows 11 Home China (10.0.26200),

Provider and model

No response

Logs or error output

Screenshots and supporting files

No response

Redacted configuration

Checks

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

Activity

  1. added
    cliCLI, config inject, packaging flags
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    serviceService lifecycle (WinSW/launchd/scheduler)
    on Sep 10, 2026
  2. github-actions commented on Sep 10, 2026

    @github-actions
    Contributor

    Issue closed: insufficient detail

    Thanks for taking the time to submit this issue. It was closed automatically because the structured report is missing information needed to evaluate or reproduce it.

    • Summary is empty.

    Please edit the issue to add:

    • Describe what happened (the symptom or error).

    See the Contributing guide for details.

    Once the report passes the automated checks, it will be reopened automatically unless a maintainer has changed its state.

  3. Ingwannu commented on Sep 10, 2026

    @Ingwannu
    Owner

    The code-path gap is present on current dev f94dd88. registerFreshWindowsSchedulerTask derives replace=false when there is no existing registration, so buildWindowsSchtasksCreateArgsForXml omits /f. isOwnedSchedulerCreate currently requires /f, and toWindowsSchtasksError consequently classifies that fresh-create failure as other. The registration catch only enters its consent-based elevation fallback for the structured access-denied reason.

    One correction: the /f gate precedes the structured classifier's English/German text test as well as its exit-status fallback. An English denial may still receive friendly wording from formatWindowsSchtasksError, but that does not change the structured reason used by the catch. So this is not necessarily limited to non-English installations.

    The fix must keep fresh registration separate from replacement: do not add /f to fresh installation to make the classifier pass, and preserve staged-XML ownership validation, explicit UAC consent, captured-definition checks, and owned rollback. Tests should cover fresh/replace, recognized text/opaque localized output, unrelated tasks/operations, and already-elevated or unknown-elevation states. No actual scheduler, UAC, antivirus, or service action was run here.

    The quality bot closed this because the Summary section is empty even though the detailed explanation appears below it. Please put a short symptom paragraph directly under that heading; the bot says it will reopen after the structured report passes. This closure should not be interpreted as a technical rejection of the finding.

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 workingcliCLI, config inject, packaging flagsplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)serviceService lifecycle (WinSW/launchd/scheduler)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions