Skip to content

Windows: bare ocx service on an already-installed service fails with WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED instead of repairing/restarting #2287

Closed

Description

@lidge-jun

Client or integration

Codex CLI

Area

Service lifecycle

Summary

On Windows, running ocx service while the Task Scheduler service is ALREADY installed and running fails with WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED instead of refreshing/restarting the existing service. The bare ocx service path unconditionally goes through install (schtasks /create /f), which requires elevation, so a routine dogfooding restart (update source, then ocx service) dies on UAC even though nothing about the registration needs to change.

Expected behavior: when the service is already installed, ocx service (no subcommand) should take the repair/restart path that ocx service repair provides (no admin re-prompt), or at minimum detect the existing registration and fall back to repair instead of failing with an access-denied error. Additionally there is no ocx service restart subcommand, which is the verb an operator reaches for first; the usage line offers install|repair|start|stop|status|uninstall|remove.

Reproduction

  1. On Windows, install the background service once (elevated): ocx service -> approve UAC. Service runs fine (ocx service status reports Task Scheduler enabled, proxy on port 10100).
  2. Update the source checkout the service points at (dogfooding path: git pull on dev).
  3. From a NON-elevated PowerShell, run ocx service to pick up the new code.
  4. Observe the failure below; the service is not restarted.
  5. ocx service restart is rejected as an unknown subcommand.

ocx service repair is the documented no-admin path, but the bare ocx service invocation (which the CLI describes as "installs/updates and starts the background service") does not route an already-installed service to it.

Version

2.27.0 (local dev checkout, service installed via Task Scheduler)

Operating system

Windows 11

Provider and model

No response

Logs or error output

PS C:\Users\super> ocx service
[X] Service install cleanup failed: Windows access denied while running Task Scheduler. Command: schtasks /create /tn opencodex-proxy /xml C:\Users\super\.opencodex\opencodex-service-task.xml /f Approve the Windows UAC prompt to install the background service, or run `ocx service install` from an elevated PowerShell window.
OCX_ERROR_CODE=WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED

PS C:\Users\super> ocx service restart
Usage: ocx service [install|repair|start|stop|status|uninstall|remove] [--native|--scheduler]
       With no subcommand, installs/updates and starts the background service.
       repair: refresh assets and restart an already-installed service (no admin re-prompt).

Screenshots and supporting files

No response

Redacted configuration

No response

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 Aug 21, 2026
  2. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Confirmed against current dev@27764f34259023d88ebe1cdc63ecb13e34d2ab64.

    The report is correct: normalizeServiceSubcommand(undefined) currently forces the bare command to install, and the Windows scheduler install path can reach schtasks /create /f even when the service is already registered. That turns a routine non-elevated refresh/restart into WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED.

    I will keep the fix narrow:

    • bare ocx service will use the existing no-admin repair path when a service is already installed, while preserving first-time install behavior when none exists;
    • ocx service restart will be an explicit alias for that same repair/restart path rather than a second lifecycle implementation;
    • --native / --scheduler will remain install-only and will not silently switch an installed backend;
    • focused regressions will cover the bare installed/uninstalled decision, the restart alias, and the CLI help/usage contract.

    I will open the implementation against dev and keep this issue open until the PR has the required independent maintainer approval and exact-head CI.

  3. lidge-jun commented on Aug 21, 2026

    @lidge-jun
    OwnerAuthor

    리뷰 · 우선순위 58 / 80

    지금 dev HEAD d23c3179f에서도 그대로임. src/service.ts:3276-3278 normalizeServiceSubcommand(undefined)가 "install"로 고정됨. parseServiceArgs (:3290-3308)가 그걸 serviceCommand에 넘김. 헬프는 "no subcommand, installs/updates and starts"라고 쓰는데 (:3461-3463) 이미 깔린 스케줄러는 업데이트 경로가 아님. Windows 스케줄러 case "install" (:3344-3366)은 scheduler.status === "absent"일 때만 installFreshWindowsSchedulerSafely(). 이미 있으면 installServiceSafely(backend, ops.install)로 감. 그게 prepareServiceInstall (:2834)로 살아있는 매니저/프록시를 먼저 죽임. 그다음 installWindows() (:2257-2266)가 schtasks(buildWindowsSchtasksCreateArgs())를 탐. 인자는 /create /tn opencodex-proxy /xml ... /f (:1632-1634, TASK = "opencodex-proxy" :53). 비관리자 셸이면 WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED (src/lib/windows-elevation.ts:233-234). 본문 로그랑 같음. ㅋㅋ 소스 땡긴 다음 재시작인데 설치 트랜잭션으로 들어감.

    더 큼. 실패가 커맨드만 빨갱이인 게 아님. prepareServiceInstall이 /create보다 앞임. 이미 돌던 스케줄러를 멈추고, schtasks /create /f가 UAC에서 죽음. 캐치 (:3367-3370)가 "Service install cleanup failed"를 찍음. 살아 있던 프록시가 그 상태로 남을 수 있음. ocx service repair가 우회인 건 맞는데, 배어 커맨드가 그 우회를 타기 전에 한 번 죽임.

    이미 있는 길이 repairService임 (:2292-2322). 주석이 그거임. Windows scheduler는 에셋 다시 쓰고 stop/start. schtasks /create 없음. UAC 없음. serviceReinstallArgs() (:276-278)도 이미 ["service", "repair"]를 줌. 업데이트 경로가 repair인 걸 코드가 알고 있음. 배어 ocx service만 모름. ocx service restart는 default 분기로 떨어짐 (:3460). 동사 없음. 별도 라이프사이클을 만들 일 아님. repair 별칭이면 됨. Ingwannu가 이슈에 박은 범위랑 같음. --native/--scheduler는 이미 install-only (:3317-3319). 깔린 백엔드를 배어 커맨드가 조용히 바꾸면 안 됨.

    테스트가 버그를 잠그고 있음. tests/service.test.ts:92-95가 normalizeServiceSubcommand() === "install"을 기대함. 배어 커맨드 = 설치라고 고정. 이미 설치된 분기에서 repair로 가는 테스트는 없음. fresh Windows scheduler wiring (:1451-1458)은 absent일 때만 fresh 트랜잭션인 것만 봄. 있는 태스크의 /create /f는 안 봄. repair 거절 테스트 (:1688-1706)는 미설치/충돌만 봄. 회귀는 (1) 설치됨 → 배어 커맨드 = repair (2) 없음 → install (3) restart = repair 별칭 (4) 백엔드 플래그는 install-only 유지. 이 네 장임.

    types.ts/config.ts 안 만짐. 스플릿 안 씹힘. 리베이스하지 말고 닫으라는 케이스 아님. 닫을 중복 아님. 구현 PR 아직 없음. 이 이슈가 계약 소유자임. #2188 L1–L9 사이드카 이미 dev. x_search 넣지 말 것. Grok OAuth Chat 기본(#2255)/GUI 옵트인 Responses(#2266)/#2283 웹서치 필드랑 다른 레인임. 프리뷰 배포 아님. v2.29.0 이미 태그됨. 핫 크래시는 아닌데 Windows 도그푸딩이 git pull 다음 ocx service 한 방에 프록시를 죽이고 UAC에서 죽음. 그래서 58.

    해결방안: 배어 ocx service는 이미 설치돼 있으면 기존 no-admin repair. 없으면 지금 install. restart는 그 repair의 별칭. 백엔드 스위칭은 install-only 유지. 배어 리프레시에서 prepareServiceInstall 부르지 말 것. 그건 /create 전에 살아 있는 서비스를 죽임. 위 네 장 회귀로 잠가라. 스플릿이 service.ts를 옮기면 리베이스하지 말고 닫고 다시 짜라. 지금은 그 정도 아님.

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

  4. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Implemented the verified service-selection fix in draft PR #2289.

    The patch keeps first-time behavior unchanged, but a bare ocx service now detects an existing registration and uses the no-admin repair/restart path instead of attempting Windows Task Scheduler registration again. It also adds ocx service restart as an explicit alias of repair and updates the maintained CLI docs.

    Verification on the rebased head:

    • Bun 1.4.0 focused service/help suites: 173 passed, 0 failed
    • typecheck: passed
    • privacy scan: passed
    • docs build: 393 pages built

    The PR is intentionally Draft and requests independent review from @lidge-jun. I will not self-approve or merge it.

  5. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Status update: draft PR #2289 is now green at exact head 2df92a27038dd6c8de264da2369a59462c65c6ca. The final implementation handles the Grok review requirements: existing installs select no-admin repair, proven absence selects install, an unknown Windows registration state fails closed, and invalid flags are rejected before probing. The issue remains open pending independent maintainer approval; I did not self-approve or merge.

  6. lidge-jun commented on Aug 22, 2026

    @lidge-jun
    OwnerAuthor

    Fixed by PR #2289 with docs-sync hardening (174f03b), landed on dev via merge train PR #2325. Unknown installation state now fails closed with guidance instead of collapsing into absent and triggering elevated re-registration.

  7. Xxingwan11 commented on Sep 12, 2026

    @Xxingwan11

    Follow-up: this appears to be caused by the generated LogonTrigger lacking a UserId, rather than /f inherently requiring elevation. Minimal A/B reproduction and source-level evidence are documented in #4425. Adding UserId to the same-user logon trigger succeeds without elevation.

    The repair/restart routing fix from #2289 is still the right change — this is a separate cause of the same symptom, on the fresh-install path (scheduler.status === "absent"), which #2289 does not cover.

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