fix(codex): name the native-main gate reason when the fence returns 503 - #2121
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
✅ Deterministic PR hygiene checks passed. |
A Windows reboot can leave native-main admission fenced until the user runs ocx restart, and the report of it was unactionable: every 503 carries the same generic maintenance message, so the settled gate reason was never written anywhere a user could read it. Two candidate triggers settle to different reasons - an ACL fail-closed settles owner-unavailable, a probe fail-closed settles ownership-unknown - and nothing on the request path could tell them apart. Capture the reason in the CodexMainProfileDrainingError constructor and warn once per distinct reason. The constructor is the last moment the reason is both in scope and still true: every catch site has already lost it, and re-reading the gate later can observe a recovery that completed in between. Three surfaces were considered and rejected. The message cannot carry it - claude-messages.ts matches that string exactly to keep the fence a 503 instead of remapping it to an Anthropic 529, so a changed message would tell Claude Code to back off from an upstream that was never involved. A response header cannot reach /api/logs, which reads only error.message out of the body, and the Claude surface rebuilds its headers from scratch. Threading a reason into codexMainProfileDrainingResponse would have meant editing all five of its zero-arg call sites. stdout reaches every path this fence fires on, and is the stream the original report was already quoting from. The turn-drain claim race throws this same error while the gate reads ready. That site stays silent rather than borrowing a startup reason, which makes a reasonless 503 mean "not the startup fence" - a distinction the report could not make. Only the reason is logged; the snapshot's homeId is derived from a profile directory path. Refs #2108 Verification: red-driven with the fix staged as a no-op export (2 fail on reason === undefined, 1 pass on the silence case), then 80 pass / 0 fail / 470 expect() across the five native-main gate suites, 124 pass / 0 fail on the Claude and chat surfaces, tsc --noEmit exit 0, privacy:scan exit 0. An independent reviewer mutation-tested the new assertions in a scratch tree and confirmed each goes red when the behavior it pins is removed. Suites ran on macOS, so the owner-unavailable icacls branch is proven as plumbing rather than field behavior.
2c4e00e to
b571284
Compare
05628d4 to
50c6ccd
Compare
…ed the first theory
리뷰 · 우선순위 70 / 80#2108 재부팅 후 native-main이 막히면 전부 같은 503이라 원인이 안 보임. ACL fail-closed는 메시지에 이유를 넣으면 안 됨. 턴 드레인 레이스는 게이트가 베이스가 macOS에서만 돌려서 해결방안: #2118 먼저 머지하고 리타깃. 로그 포맷 고정해서 다음 #2108 리포트가 사유를 붙이게. 503 문구/헤더는 그대로. 이 댓글은 grok-bot이 작성했습니다 |
Summary
A Windows reboot can leave native-main admission fenced until the user runs
ocx restart. #2108 reports it, but the report could not be acted on: every fenced request returns the same generic 503 maintenance message, so the gate reason that actually settled was never written anywhere a user could read.That matters because the two candidate triggers settle to different reasons — an ACL fail-closed settles
owner-unavailable(native-profile-startup.ts:138-139), a probe fail-closed settlesownership-unknown(:311) — and nothing on the request path distinguished them. This ships the diagnostic, not the mechanism fix; guessing between two candidates without evidence is how the next reboot report ends up exactly as ambiguous as this one.CodexMainProfileDrainingErrornow captures the settled reason in its constructor and warns once per distinct reason. The constructor is the last moment the reason is both in scope and still true — every catch site has already lost it, and re-reading the gate later can observe a recovery that completed in between.Three other surfaces were considered and rejected, which is why this is a log line rather than something more structured:
claude-messages.ts:818-820matches that string exactly to keep the fence a 503 instead of remapping it to an Anthropic 529. A changed message would tell Claude Code to back off from an upstream that was never involved./api/logs, which populatesupstreamErrorby readingjson.error.messageback out of the body (request-log.ts:755-759);error.codeis likewise pinned toserver_is_overloadedfor 503 by design (request-log.ts:508-540). The Claude surface also rebuilds its response headers from scratch (claude-messages.ts:823-829).codexMainProfileDrainingResponsewould mean editing all five of its zero-arg call sites, two of which are also edited by open PR fix(codex): gate account-native models by entitlement #2101.stdout reaches every path this fence fires on, and it is the stream the original report was already quoting from.
The turn-drain claim race (
auth-context.ts:326, driven bylifecycle.ts:180) throws this same error while the gate readsready. That site stays silent rather than borrowing a startup reason, so a reasonless 503 now means "not the startup fence" — a distinction the reporter had no way to make. Only the reason is logged; the snapshot'shomeIdis derived from a profile directory path and stays out.Refs #2108. Deliberately not
Closes: this makes the next occurrence diagnosable, it does not stop the fence from sticking. Phase 2 (making a boot-timeunknownretryable instead of a process-lifetime fence) waits for a field report that names a reason.Stacked on #2118 to keep the review surface small; retarget to
devonce the parents land.Verification
The five gate suites are
codex-auth-context,native-profile-startup,native-profile-drain-server,native-profile-stage-lifecycle,native-main-owner-lifetime.privacy:scanis not ceremony here — this PR adds a log statement, which is what that scan exists to police.Red-driven, not merely green. With the fix staged as a no-op export, two assertions failed on
reason === undefinedand the turn-drain silence case passed — proving the assertions themselves were doing the work rather than a module-load error. An independent reviewer then mutation-tested the new tests in a scratch copy: removing theconsole.warnbody goes red (2 fail), and removing the dedup check goes red. Each assertion detects the removal of the behavior it claims to pin.One honest caveat: these suites ran on macOS. The
owner-unavailablebranch this diagnostic most needs to name is a Windows icacls path, so this proves the plumbing rather than the field behavior. Worth knowing that no test anywhere currently assertsowner-unavailable— it is produced atnative-profile-startup.ts:139and asserted nowhere in the suite.Checklist