Skip to content

Windows: v2.40.0 still misclassifies a valid scheduler task for non-ASCII account names #3320

Closed as not planned

Description

@chowyuan1314

Client or integration

Codex CLI

Area

Service lifecycle

Summary

On Windows 11, OpenCodex v2.40.0 can report the proxy service as expired and reboot protection as missing even when the Task Scheduler task is enabled and the proxy is running.

Expected: ocx service / ocx service repair should recognize the registered opencodex-proxy task and report the service as healthy.

Actual: the install/repair flow can fail with schtasks.exe /create ... Access is denied. from a normal PowerShell session. After elevated task registration, the health check can still reject the valid task when the Windows account name contains non-ASCII characters. The local proxy remained reachable (the health endpoint returned HTTP 200), so this appears to be a scheduler identity/trigger validation false negative rather than a proxy failure.

This looks like a residual case related to #3064 and PR #3134: paths may be handled, but the <UserId> comparison and/or schtasks output decoding still does not tolerate non-ASCII account names.

Reproduction

Environment: Windows 11 x64; the Windows account/profile directory contains non-ASCII characters. The actual account name and path are intentionally redacted.

  1. Install @bitkyc08/opencodex@2.40.0 globally.
  2. From a normal (non-elevated) PowerShell, run ocx service (or ocx service install).
  3. Observe a scheduled-task registration/cleanup error similar to:
    schtasks.exe /create /tn opencodex-proxy /xml <temporary-stage-path>\task.xml
    followed by Access is denied.
  4. Register or repair the task from an elevated shell.
  5. Refresh the OpenCodex startup-safety/service health check and observe that a valid enabled/running task can still be reported as expired or as having missing reboot protection.
  6. Verify independently that the opencodex-proxy task is running and that the local health endpoint returns HTTP 200.

No real username, machine name, SID, full path, PID, token, or raw log file is included in this report.

Version

@bitkyc08/opencodex@2.40.0

Operating system

Windows 11 x64 (Windows account/profile path contains non-ASCII characters)

Provider and model

No response

Logs or error output

No raw logs attached. Sanitized error shape:

schtasks.exe /create /tn opencodex-proxy /xml <temporary-stage-path>\task.xml
Access is denied.

Screenshots and supporting files

No screenshot or supporting file attached; local account, machine, and path details were intentionally omitted.

Redacted configuration

Not provider-specific. No credentials, tokens, account identifiers, or private configuration are relevant.

Checks

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

Activity

  1. added
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    serviceService lifecycle (WinSW/launchd/scheduler)
    on Sep 3, 2026
  2. lidge-jun commented on Sep 3, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 64 / 80

    이 이슈는 Windows에서 계정 이름(또는 프로필 경로)에 비ASCII 문자가 있을 때, Task Scheduler에 opencodex-proxy 작업이 실제로 켜져 있고 로컬 프록시 health가 HTTP 200인데도 OpenCodex가 서비스를 “만료(expired)”이거나 “재부팅 보호 없음”처럼 잘못 판정한다는 보고입니다. 재현 환경은 Windows 11 x64, 패키지는 @bitkyc08/opencodex@2.40.0이고, 일반(비관리자) PowerShell에서 ocx service / ocx service install을 돌리면 schtasks.exe /create ... Access is denied.가 나고, 관리자 권한으로 작업을 등록한 뒤에도 상태/수리 흐름이 유효한 등록을 거부할 수 있다고 합니다. 보고자가 가리킨 선행 이슈는 #3064(비ASCII 프로필 경로로 install이 롤백되던 문제)와 그 수정 PR #3134입니다.

    지금 dev HEAD(bb27c26be, package 2.41.0)를 기준으로 보면 #3134는 이미 v2.40.0에도 들어 있습니다. 그 수정의 핵심은 src/service.ts의 taskXmlLossyValueEquals / taskXmlDecodedLossyValueEquals입니다. schtasks /query /xml이 콘솔 코드페이지 밖으로 나가지 못하는 경로 글자를 ?나 U+FFFD로 바꿔 돌려보낼 때, OpenCodex가 방금 자기가 쓴 Command/Arguments(런처 경로)만 “손실 허용”으로 다시 맞춰 보게 해서 #3064식 롤백을 막습니다. 그래서 “경로만” 깨지던 케이스는 2.40.0에서도 이미 다뤄진 상태입니다. 이 이슈가 남는다면 경로가 아니라 세션 복구 트리거의 <UserId> 신원 비교 쪽입니다.

    같은 파일의 windowsTaskTriggerScopeAcceptable(대략 2117–2136행)은 의도적으로 lossy를 쓰지 않습니다. 새 등록은 resolvedWindowsTaskSid()로 SID를 XML에 넣고, 상태 판정은 cachedWindowsTaskUserIds()가 돌려주는 [sid, name]과 taskXmlDecodedValueEquals로 정확히 맞춥니다. 주석과 테스트 never code-page-folds an explicit session identity(tests/service.test.ts)가 이유를 밝힙니다. 계정 이름을 lossy로 ???까지 허용하면 비ASCII 두 계정이 같은 치환 문자열로 붕괴해, 고정 이름 작업(opencodex-proxy)을 다른 계정 것으로 수리/채택할 수 있기 때문입니다. 신원은 경로와 달리 “조금만 느슨하게”가 보안 경계입니다.

    그럼에도 보고 증상(프록시는 살아 있는데 스케줄러 정의만 stale/만료/재부팅 보호 누락)은 이 정확한 UserId 검사에서 나올 수 있습니다. 레거시/수동 등록이 이름 형태(MACHINE\비ASCII)를 <UserId>에 넣었거나, schtasks export가 이름을 코드페이지로 깨뜨린 채 SID는 없고 깨진 이름만 남은 경우, expected 쪽의 올바른 유니코드 이름·SID와 한 글자도 안 맞아 windowsTaskHasSessionRecoveryTriggers가 실패합니다. 그러면 windowsTaskRegistrationHealthy가 false가 되고 deriveWindowsServiceDiagnostic이 stale로 올라가 “재부팅 보호/세션 복구 트리거 없음”처럼 보입니다. 반대로 healthz 200은 프록시 프로세스와 무관하게 통과할 수 있어, 보고자의 “스케줄러 신원 오탐” 설명과 맞습니다. 일반 셸의 Access is denied는 별 트랙입니다. schtasks /create는 관리자 상승이 필요한 등록 경로이고, #2287 계열의 “이미 설치된 서비스에서 create를 다시 치려다 거절”과 겹칠 수 있습니다. 이 이슈의 본진은 상승 실패가 아니라, 상승 후에도 유효 등록을 건강하지 않다고 읽는 판정입니다.

    정리하면 현재 dev는 경로 mangling(#3064/#3134)과 “UserId는 접히지 않게”라는 정책을 둘 다 갖고 있고, 그 정책이 남은 오탐의 직접 원인 후보입니다. types.ts/config.ts 분할 캠페인과는 무관한 platform/service 버그라 닫지 말고, 재현 XML(redact된 <UserId> 형태: SID인지 이름인지, export가 ?로 깨졌는지)만 확보한 뒤 신원 쪽을 좁혀 고치면 됩니다.

    src/service.ts · windowsTaskTriggerScopeAcceptable - UserId는 taskXmlDecodedValueEquals만 쓰고 lossy를 금지한다. 이름형 UserId가 schtasks export에서 ???로 바뀌면 세션 복구 트리거가 전부 실패해 건강 등록이 stale/재부팅 보호 누락으로 보인다.
    src/service.ts · taskXmlLossyValueEquals / windowsTaskRegistrationBaseHealthy - #3134가 고친 것은 Command/Arguments 경로뿐이라, 보고자가 적은 “경로 말고 UserId” 잔여 가설과 코드가 일치한다. 2.40.0에 이미 들어가 있어도 이 이슈는 설명된다.
    src/service.ts · buildWindowsTaskXml / resolvedWindowsTaskSid - 신규 등록은 SID를 넣도록 바뀌었지만, 예전 이름형 등록·수동 XML·export 변형이 남아 있으면 SID expected와 이름 mangling reported가 교차해 영원히 불일치할 수 있다.
    src/lib/windows-user-principal.ts · cachedCurrentWindowsIdentity - PowerShell이 돌려주는 name은 유니코드로 올바를 수 있어, schtasks가 깨뜨린 UserId 문자열과 더 어긋난다. 디코딩 버그라기보다 비교 권위(SID vs 이름 vs 깨진 이름) 문제다.
    일반 PowerShell의 schtasks /create Access is denied - 상승이 필요한 등록 실패로, health 오탐과 원인을 섞지 말아야 한다. 재현 메모에서는 “비상승 create 실패”와 “상승 후 상태 오판”을 단계별로 나눠 적는 편이 좋다.

    메인테이너의 판단이 필요한 지점

    • UserId 비교를 SID 우선(이름이 mangling이면 SID만으로 통과)으로 완화할지, 이름형 레거시는 repair 때 SID로만 재등록하고 오탐 메시지만 고칠지
    • schtasks export의 깨진 이름을 lossy로 용서하는 범위를 열지(테스트가 금지한 방향이라 보안 검토 필요)
    • 재현에 redact XML 샘플(세션 트리거 UserId가 SID인지 이름인지)을 필수로 받을지
    • 비상승 Access is denied를 이 이슈 범위에 넣을지, elevation/UX 이슈로 분리할지

    너의 추천
    열어둠 유지. 보고자에게 schtasks /query /tn opencodex-proxy /xml에서 세션 트리거 <UserId>만 redact해 형태(SID / DOMAIN\name / ???)를 알려 달라고 한 뒤, SID가 이미 있는데도 실패하면 identity 캐시/조회 경로를, 이름형·mangling이면 repair의 identityUpgradeNeeded가 실제로 SID 재등록까지 가는지와 상태 문구를 고치는 작은 platform PR로 이어라. #3064/#3134와 중복 닫지 말 것(경로 수정과 잔여 UserId 오탐은 코드상 분리됨). types/config 분할과 무관.

    이 댓글은 grok-bot이 작성했습니다

  3. lidge-jun commented on Sep 3, 2026

    @lidge-jun
    Owner

    @chowyuan1314 — following up during a backlog review. This is not closed; it needs one piece of information from you to move.

    Where this stands. Scheduler identity requires exact decoded UserId equality at src/service.ts and deliberately refuses lossy matching, so distinguishing a SID from a display-name mismatch needs the actual value.

    What would unblock it. Please add the redacted <UserId> element from schtasks /query /xml for the affected task — shape only, with the identifier itself masked.

    If that arrives, this gets picked up in the next triage pass. Without it, the report cannot be distinguished from configurations that already work, so it will keep sitting here rather than being wrong on purpose.

  4. added
    needs-infoWaiting on reporter for a concrete spec or reproduction
    on Sep 3, 2026
  5. chowyuan1314 commented on Sep 4, 2026

    @chowyuan1314
    Author

    Thanks. I queried the currently registered opencodex-proxy task with schtasks /query /tn opencodex-proxy /xml. The redacted element is:

    <UserId>S-1-5-21-[redacted]-[redacted]-[redacted]-[redacted]</UserId>

    This is the SID form, not an account/display-name form.

    Important context: this query was performed after applying a local compatibility patch to v2.40.0 and running ocx service repair, which may have recreated the task. The stock v2.40.0 install/validation failed before that repair. Therefore this confirms the identity shape of the current task, but should not be read as proof that the unpatched build passes. No raw SID, username, machine name, or full path is included.

  6. lidge-jun commented on Sep 4, 2026

    @lidge-jun
    Owner

    @chowyuan1314 — thanks, the SID form is the useful part.

    On current dev the validator should accept exactly what you pasted. cachedWindowsTaskUserIds() in src/service.ts returns both spellings — [identity.sid, identity.name] — and windowsTaskTriggerScopeAcceptable accepts the trigger if the <UserId> matches either of them. So a SID-form <UserId> is not itself the rejection cause, and neither is a non-ASCII display name, provided the identity resolves at all.

    That narrows it to one of two things, and your note that the query ran after a patched ocx service repair is exactly why I cannot tell which from here:

    1. Identity resolution fails outright. If cachedCurrentWindowsIdentity() returns nothing, resolveWindowsTaskDiagnosticUserId falls through to resolveCurrentWindowsPrincipal, and if that also fails it returns null — at which point a scoped trigger cannot be validated and the task is reported unhealthy no matter how correct it is. A non-ASCII account name would plausibly break the lookup's output decoding rather than the comparison.
    2. The stock task differs from the repaired one. Your repair may have rewritten the trigger into a shape the stock build never produced.

    What would settle it, from an unpatched v2.42.x (or current dev), before any repair:

    • ocx service status --json — I am specifically after whether the scheduler detail says the registration is unhealthy versus the XML could not be read, since those are different branches.
    • The <Triggers> block of schtasks /query /tn opencodex-proxy /xml, redacted the same way you did — I need to see whether <UserId> is present and whether the element is namespace-prefixed (<t:UserId>), because a prefixed tag is rejected outright by taskXmlHasPrefixedTag.
    • Whether ocx service status behaves differently in an elevated versus non-elevated shell.

    If the identity lookup is what fails on a non-ASCII account, that is a real bug and I would rather fix the lookup than the comparison. Keeping this open and labeled needs-info in the meantime.

  7. lidge-jun commented on Sep 8, 2026

    @lidge-jun
    Owner

    Closing as stale needs-info. This is not a judgment that the report was invalid.

    The evidence needed to move it is a pre-repair capture from an unpatched build: the
    ocx service status --json scheduler detail, and the <Triggers> block from
    schtasks /query /tn opencodex-proxy /xml, redacted the way you already did. The SID you shared
    was queried after a local compatibility patch and an ocx service repair, which may have rewritten
    the task, so it confirms the current shape rather than the failing one.

    That distinction matters because a SID-form <UserId> should already validate on current dev:
    cachedWindowsTaskUserIds() returns both the SID and the account name, and
    windowsTaskTriggerScopeAcceptable accepts a trigger matching either. The remaining candidates are
    that identity resolution fails outright on a non-ASCII account, or that the stock task differs from
    the repaired one — and only an unpatched capture separates them.

    Please reopen with that capture and it will be picked up. Thank you for confirming the SID shape and
    for redacting it carefully.

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 workingneeds-infoWaiting on reporter for a concrete spec or reproductionplatformOS/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