Skip to content

Windows: a second OpenCodex home can never pass admitCodexWrite once Task Scheduler holds opencodex-proxy #2800

Closed

Description

@adtumk

Client or integration

CLI

Area

Codex sync / service ownership

Summary

On a machine where the opencodex-proxy scheduled task is registered, any second OpenCodex
home is permanently refused write admission — not blocked by a race or a stale lock, but by the
ownership model itself. ocx sync can never complete from that home, so its Codex config is
never injected and its proxy never reaches ready.

Scheduler registration is machine-global. The task-definition XML that proves ownership is
per-home. A second home therefore sees "the scheduler holds the task" and "my task XML is absent"
simultaneously, which the probe reports as unproven ownership.

Reproduction

  1. On a machine where OpenCodex is installed normally and the opencodex-proxy scheduled task is
    registered from home A.
  2. Create an unrelated home B (HOME / USERPROFILE / CODEX_HOME pointing at a fresh
    directory) with a minimal .opencodex/config.json.
  3. Start a proxy from home B on a free port. It starts and /healthz is ok.
  4. Run ocx sync from home B.

Actual

ocx sync prints:

Codex sync did not complete. Fix the reported Codex config issue and retry.

…and exits 1. Nothing is written. Calling the admission gate directly shows the real reason:

{
  "kind": "refused",
  "authority": "service-home",
  "message": "Refusing to write because ownership could not be proven: Task Scheduler holds opencodex-proxy but its task XML is missing."
}

/readyz on the home-B proxy stays failed indefinitely, because readiness is gated on the
post-startup sync that just refused. /healthz stays ok, so the proxy looks healthy while
nothing it needs to inject has been injected.

Expected

Either:

  • ownership is evaluated per home, so a home that does not claim the scheduled task is not judged
    against another home's registration; or
  • the refusal is explicit and actionable — it names the owning home, and ocx sync surfaces the
    reason and exits non-zero.

Why this is hard to diagnose

The admission gate produces a precise message and the CLI discards it, so the operator sees an
instruction ("fix the reported Codex config issue") referring to a report that was never printed.
The process does exit non-zero, but the actionable refusal reason is still hidden. #1461 fixed the
injection-preflight diagnostic, but this refusal comes from the earlier machine-global service-home
admission gate, whose message still never reaches the operator.

Impact

Any multi-home use of the CLI on one machine: staging or a second profile alongside a normal
install, CI on a developer machine, or testing a candidate build without disturbing the working
one. There is no configuration that avoids it, because the collision is between a machine-global
registration and a per-home proof.

Notes

Found while validating an unrelated change in an isolated second home; the workaround there was
the code's own CodexSyncDeps.admitCodexWrite test seam, which is not available to operators.

Correction after a clean re-run: an earlier version of this report said ocx sync exited 0. The
real CLI exits 1. The earlier 0 came from reading the status of a later command in a shell pipeline,
not the ocx sync process itself. The ownership refusal and swallowed diagnostic reproduce
unchanged.

Version

Reproduced again against upstream dev @ 80e251a4db5ab600ef0a0b44c9d24b326c37d40b (2.35.0),
built from source in an isolated dev/staging environment (isolated home, isolated port). This is
after #2815; that Windows sync fix does not change this service-home admission path, and the issue
still reproduces.

Operating system

Microsoft Windows 11 Pro, version 25H2, build 26200.9168, 64-bit.

Activity

  1. added
    bugSomething isn't working
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    on Aug 28, 2026
  2. github-actions commented on Aug 28, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  3. lidge-jun commented on Aug 28, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 68 / 80

    설명

    Windows에서 opencodex-proxy 예약 작업이 이미 등록된 머신에, 다른 OpenCodex home B를 새로 만들면 ocx sync가 영원히 쓰기를 거절당하는 버그입니다. 스케줄러 등록은 머신 전역이고, 소유를 증명하는 task XML은 home마다 있습니다. home B는 “스케줄러에 작업이 있다”와 “내 task XML은 없다”를 동시에 보게 되고, src/service-manager-probe.ts가 그 상태를 unknown("Task Scheduler holds opencodex-proxy but its task XML is missing")으로 돌립니다(현재 HEAD 약 L683-686). admitCodexWrite(src/codex/admission.ts L132-140)는 ownership이 owned가 아니면 authority: "service-home"으로 거절합니다. syncModelsToCodex는 이 거절을 status: "refused", ok: false로 반환합니다. 그 결과 home B 프록시는 /healthz는 ok인데 주입이 안 되어 /readyz는 실패로 남는 “겉만 건강한” 상태가 됩니다. 멀티 홈·사이드 프로필·테스트 홈을 Windows에서 쓰려는 순간 바로 밟히는 platform 버그라서 우선순위가 높습니다. 리포터는 exit 0이라고 적었는데, 현재 src/cli/dispatch.ts는 !synced.ok이면 code = 1로 둡니다. 재현 때 exit code를 다시 확인하고, 설령 1이어도 거절 message를 콘솔에 안 찍고 일반 문구만 보여주는 UX는 따로 고쳐야 합니다.

    src/service-manager-probe.ts 약 L683-686 - 스케줄러 present + 로컬 task XML absent = unknown. 다른 home 소유인데도 “내 소유 미증명”으로 묶인다.
    src/cli/dispatch.ts 약 L226-228 - sync 실패 시 고정 문구만 출력하고 synced.message(거절 이유)를 안 보여 준다.
    /healthz ok + /readyz failed - 운영자가 health만 보면 원인을 못 찾는다. ready/동기화 문서에 Windows 멀티홈 한계를 명시할지 결정이 필요하다.

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

    • 소유권 모델을 “머신당 하나의 서비스 home”으로 유지할지(그 경우 거절은 맞지만 메시지에 owning home 경로를 넣어야 함).
    • 아니면 “이 home이 스케줄러를 주장하지 않으면 서비스-홈 검사를 건너뛰고 해당 home만 sync”하도록 완화할지.
    • 단기 완화로 ocx sync에 거절 reason·exit code를 분명히 노출하는 UX 픽스를 먼저 넣을지.

    너의 추천
    bug/platform 맞습니다. 먼저 dispatch가 admission.message를 인쇄하게 하는 작은 UX 픽스를 받고, 소유 모델(엄격 유지 vs 홈 단위 완화)은 짧은 설계 메모 후 구현 PR을 받으세요. preview 배포와는 무관합니다.

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

  4. adtumk commented on Aug 28, 2026

    @adtumk
    ContributorAuthor

    Rechecked the point raised in review against current dev (80e251a4db5ab600ef0a0b44c9d24b326c37d40b, after #2815). The core second-home/service-home refusal still reproduces, but the original exit-code claim was wrong: the real ocx sync process exits 1, not 0. The earlier 0 came from reading the status of a later command in a shell pipeline. I updated the issue body accordingly. The actionable admission reason is still swallowed by the CLI, and /healthz remains ok while /readyz stays failed.

  5. lidge-jun commented on Aug 28, 2026

    @lidge-jun
    Owner

    Fixed on dev by commit 878d986 (PR #2857).

    You were right, and the reasoning turned out to be exactly what you described. The service-manager probe treated a Task Scheduler registration as a machine-global fact: once opencodex-proxy was registered anywhere, a second OPENCODEX_HOME whose own task XML was absent got back unknown, and admitCodexWrite has no choice but to refuse an unknown verdict. So the second home was permanently locked out rather than temporarily.

    The probe now decides ownership per home: a registered launcher pointing outside the effective OPENCODEX_HOME is recognised as another home's claim rather than an unresolvable state. Missing or malformed evidence for the current home still fails closed, so this does not weaken the guard it was protecting.

    Verified with a fixture reproducing your exact second-home scenario, driven RED (it returned the unknown reason you reported) then GREEN, plus a mutation check confirming the old decision fails the new regression — 83 pass across three focused files, tsc and privacy:scan clean.

    One honest caveat: no real Windows 11 machine was available here, so this is verified by fixture and source reasoning rather than an end-to-end Windows smoke test. If you can try a build from dev, confirmation would be welcome.

    This PR targeted dev, so GitHub did not auto-close the issue — closing manually.

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 workingplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions