Repository navigation
Windows: a second OpenCodex home can never pass admitCodexWrite once Task Scheduler holds opencodex-proxy #2800
Description
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
- On a machine where OpenCodex is installed normally and the
opencodex-proxyscheduled task is
registered from home A. - Create an unrelated home B (
HOME/USERPROFILE/CODEX_HOMEpointing at a fresh
directory) with a minimal.opencodex/config.json. - Start a proxy from home B on a free port. It starts and
/healthzisok. - Run
ocx syncfrom 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 syncsurfaces 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
github-actions commentedon Aug 28, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
lidge-jun commentedon Aug 28, 2026
리뷰 · 우선순위 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.tsL132-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(거절 이유)를 안 보여 준다./healthzok +/readyzfailed - 운영자가 health만 보면 원인을 못 찾는다. ready/동기화 문서에 Windows 멀티홈 한계를 명시할지 결정이 필요하다.메인테이너의 판단이 필요한 지점
ocx sync에 거절 reason·exit code를 분명히 노출하는 UX 픽스를 먼저 넣을지.너의 추천
bug/platform맞습니다. 먼저 dispatch가admission.message를 인쇄하게 하는 작은 UX 픽스를 받고, 소유 모델(엄격 유지 vs 홈 단위 완화)은 짧은 설계 메모 후 구현 PR을 받으세요. preview 배포와는 무관합니다.이 댓글은 grok-bot이 작성했습니다
adtumk commentedon Aug 28, 2026
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 realocx syncprocess 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/healthzremainsokwhile/readyzstays failed.lidge-jun commentedon Aug 28, 2026
Fixed on
devby 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-proxywas registered anywhere, a secondOPENCODEX_HOMEwhose own task XML was absent got backunknown, andadmitCodexWritehas no choice but to refuse anunknownverdict. 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_HOMEis 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
unknownreason you reported) then GREEN, plus a mutation check confirming the old decision fails the new regression — 83 pass across three focused files,tscandprivacy:scanclean.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.