Skip to content

fix(codex): isolate provider host transport health - #922

Draft
luvs01 wants to merge 20 commits into
lidge-jun:devfrom
luvs01:fix/914-account-neutral-network
Draft

fix(codex): isolate provider host transport health#922
luvs01 wants to merge 20 commits into
lidge-jun:devfrom
luvs01:fix/914-account-neutral-network

Conversation

@luvs01

@luvs01 luvs01 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • track shared pre-status transport health by provider plus canonical HTTP(S) origin, independently from account health
  • enforce a bounded host circuit with a three-failure/five-minute window, 30-second cooldown, generation-fenced leases, and one half-open logical request
  • preserve ordered physical-attempt evidence, including 503 -> rejection, across regular Responses and native Compact without changing the fix(compact): try one alternate account on a pool 429/402, and keep the backoff headers #927 primary ladder or single-send alternate
  • keep credential-bearing redirects manual and bounded, hide Location, and never replay a credential-consuming no-status rejection under another account
  • document the host/account boundary and existing-affinity/active-account effects in all five shipped locales

Root cause

The pool previously had only account-scoped transport health. A terminal rejection before any HTTP status therefore looked like evidence against the selected credential even when every account shared the same unreachable origin. Runtime error labels are not a reliable ownership boundary: Bun can expose DNS and refused-port failures with the same label, and timeout, redirect, read-close, and retry histories add further ambiguity.

This revision uses an independent process-local host ledger keyed by provider and canonical HTTP(S) origin. Admission leases carry a generation so stale completions cannot close or reopen a newer circuit, active half-open leases cannot be evicted, and concurrent ordinary leases settle without invalidating siblings.

Behavior and scope

  • An unbound pool route acquires host admission before account selection, so an open circuit is selection-neutral.
  • A fixed account selector resolves credentials first, preserving missing-credential 401 precedence, then checks host admission before dispatch.
  • A terminal logical request that rejects before any HTTP status updates only host health. It does not change account cooldown, failure streak, affinity, rotation, or active selection.
  • A real HTTP response remains account evidence. If a later physical retry rejects, ordered history preserves the response outcome for the account and the terminal rejection for host health.
  • An open circuit returns a bounded 502 with Retry-After; after cooldown, exactly one logical request owns the half-open lease.
  • Regular Responses and native Compact share the policy while retaining their distinct account-outcome and recovery-probe settlement rules.
  • Post-header body/SSE failures and sidecar/search/image lifecycles remain outside this PR.

Verification

Official-runtime checks used Bun 1.3.14+0d9b296af on exact head d6c373439e3c12ad595cfb14e70e609419695980, based on dev at e44d234f08e03dd4dbf0c4aa13af43046d86b0a6.

  • focused host/routing matrix across six files
    • 190 passed in the combined run; the only default-budget failure was one Windows test that took 5.011s against a 5s limit
    • that file passed 19/19 (77 assertions) in isolation with a 15s test budget; all 191 focused cases are functionally green
    • matrix assertion count: 1,846
  • latest account-catalog integration matrix
    • 72 passed in the combined run; four catalog subprocess cases exceeded the default 5s limit
    • the catalog file passed 17/17 (100 assertions) in isolation with a 15s test budget; all 76 integration cases are functionally green
  • bun run typecheck
    • passed
  • bun run privacy:scan
    • passed
  • git diff --check
    • passed
  • bun run build from docs-site/
    • passed, 216 pages built
  • exact rebase comparison
    • all 20 commits are range-diff equivalent after integrating current dev
  • independent exact-diff and post-rebase semantic reviews
    • no P0-P3 findings
Full-suite environment note

The official Bun 1.3.14 full suite stopped with Bun's own internal assertion in api-storage-policy-run.test.ts. The same single-file panic and report URL reproduced on clean current dev, so it is not a branch-only product failure.

As supplemental evidence, installed 1.4.0-canary.1+5f65d3785 completed the full traversal (8,215 pass, 6 skip, 43 fail, 2 errors) but ran about 2,480s versus the runner's normal ~210s. Its Windows ACL, EBUSY, and timeout failure classes reproduced on clean dev; the #922 files that appeared in the loaded full run passed when isolated. The canary result is not presented as the official project runtime.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Fixes #914.

Related: #919 and #915 cover separate post-header and recovery-probe policies. #966 remains an overlapping alternative; the accepted evidence and intentional design differences are recorded in the comparison comment.

Summary by CodeRabbit

  • Bug Fixes

    • Improved upstream failover handling by distinguishing account-level failures from host connectivity failures.
    • Added host circuit breaking, cooldown and half-open recovery behavior to prevent repeated requests to unhealthy hosts.
    • Improved redirect, timeout, retry, cancellation and transport-error handling while preserving account affinity.
    • Prevented replay of consumed credential requests and ensured recovery probes are released reliably.
  • Documentation

    • Clarified upstreamFailoverThreshold behavior and failure classification across supported languages.

@github-actions github-actions Bot added the bug Something isn't working label Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds Codex upstream-host health tracking, admission leases, circuit breaking, and retry-attempt observations. Standard and compact Responses separate transport outcomes from account failures. Tests and localized documentation cover redirects, timeouts, retries, replay restrictions, and host-level failure handling.

Changes

Codex upstream host health

Layer / File(s) Summary
Host health state and admission
src/codex/upstream-host-health.ts, tests/codex-upstream-host-health.test.ts
Adds canonical host keys, bounded storage, leases, cooldowns, half-open admission, failure settlement, response clearing, redirect classification, and eviction tests.
Retry attempt observations
src/lib/upstream-retry.ts, tests/upstream-transient-retry.test.ts
Adds attempt observations, response-status lookup, safe callbacks, and retry evidence tests.
Standard response transport integration
src/server/responses/core.ts, src/codex/routing.ts
Adds host admission and outcome tracking. Retry handling distinguishes pre-execution failures, responses, cancellations, and connection failures across alternate-account flows.
Compact response transport integration
src/server/responses/compact.ts
Adds host admission, manual redirects, attempt observations, cancellation cleanup, and lease settlement for primary and alternate sends.
Runtime behavior validation
tests/codex-host-health-runtime.test.ts, tests/issue-452-empty-503.test.ts, tests/responses-compaction-routing.test.ts, tests/server-auth.test.ts, tests/helpers/isolated-codex-home.ts
Tests circuits, refused connections, timeouts, redirects, consumed-request restrictions, mixed outcomes, account attribution, probe cleanup, and neutral transport failures.
Failover threshold documentation
docs-site/src/content/docs/*/reference/configuration/providers.md
Documents account-scoped failure counting, host circuits, redirects, retry restrictions, and post-200 behavior in five locales.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Responses
  participant HostHealth
  participant Upstream
  participant AccountPool
  Client->>Responses: send Codex request
  Responses->>HostHealth: acquire host admission lease
  HostHealth-->>Responses: allow or block request
  Responses->>Upstream: execute request and retries
  Upstream-->>Responses: response or transport observation
  Responses->>HostHealth: record host response or failure
  Responses->>AccountPool: record account outcome when applicable
  Responses-->>Client: return upstream response or 502
Loading

Possibly related PRs

Suggested reviewers: ingwannu, wibias, lidge-jun

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states that Codex provider host transport health is being isolated, which matches the primary change.
Linked Issues check ✅ Passed The changes implement provider-host health isolation, account-neutral transport failures, lease cleanup, routing protection, regular and compact coverage, and documentation for issue #914.
Out of Scope Changes check ✅ Passed The implementation, tests, localized documentation, and Windows test cleanup directly support the host-health isolation objectives and contain no unrelated changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 888c9558b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs-site/src/content/docs/reference/configuration/providers.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-site/src/content/docs/reference/configuration/providers.md`:
- Line 25: Update the upstreamFailoverThreshold documentation to explicitly
describe account-neutral failures as runtime-classified exact pre-connect DNS
and reachability errors within a bounded cause chain, and state that timeouts,
aborts, resets, socket closures, HTTP failures, and semantic upstream failures
remain account-scoped. Apply equivalent wording in
docs-site/src/content/docs/reference/configuration/providers.md lines 25-25,
docs-site/src/content/docs/ja/reference/configuration/providers.md lines 24-24,
docs-site/src/content/docs/ko/reference/configuration/providers.md lines 24-24,
docs-site/src/content/docs/ru/reference/configuration/providers.md lines 25-25,
and docs-site/src/content/docs/zh-cn/reference/configuration/providers.md lines
24-24, translating the qualifiers appropriately for each locale.

In `@src/lib/upstream-retry.ts`:
- Around line 32-53: Update the ACCOUNT_NEUTRAL_NETWORK_ERROR_CODES and
ACCOUNT_SCOPED_CONNECTION_ERROR_CODES sets to match Bun 1.3.14: retain ENOTFOUND
and the existing supported codes, and remove DNSResolveFailed,
DNSResolutionFailed, Timeout, Aborted, AbortedBeforeConnecting, and
ClientAborted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8aa7417e-a90c-4725-b3f6-750bc7b24b6d

📥 Commits

Reviewing files that changed from the base of the PR and between fa51fce and 888c955.

📒 Files selected for processing (10)
  • docs-site/src/content/docs/ja/reference/configuration/providers.md
  • docs-site/src/content/docs/ko/reference/configuration/providers.md
  • docs-site/src/content/docs/reference/configuration/providers.md
  • docs-site/src/content/docs/ru/reference/configuration/providers.md
  • docs-site/src/content/docs/zh-cn/reference/configuration/providers.md
  • src/lib/upstream-retry.ts
  • src/server/responses/compact.ts
  • src/server/responses/core.ts
  • tests/codex-account-neutral-network.test.ts
  • tests/server-auth.test.ts

Comment thread docs-site/src/content/docs/reference/configuration/providers.md Outdated
Comment thread src/lib/upstream-retry.ts Outdated
@lidge-jun

Copy link
Copy Markdown
Owner

Reviewing this against a runtime probe, because I spent four audit rounds on the same problem in a planning unit and want to hand over what killed each attempt rather than have you rediscover it.

Including ConnectionRefused is the right call and it is the thing my first design missed. Bun's fetch does not surface Node codes at all, so a classifier built only on ENOTFOUND/EAI_AGAIN is green in CI against an injected Object.assign(err, {code}) and dead in production. You caught that; I did not, until a reviewer probed the runtime.

But there is a consequence worth deciding deliberately. On Bun 1.3.14 a nonexistent hostname and a refused port are indistinguishable from the error object:

fetch("https://no-such-host.invalid/x")  -> code: "ConnectionRefused", errno: 0, no cause
fetch("http://127.0.0.1:1/x")            -> code: "ConnectionRefused", errno: 0, no cause

Same code, same errno, no cause on either. So ConnectionRefused in the neutral set means a genuinely refused connection to a resolving upstream host also stops counting against account health. That may well be what you want — every pool account shares the host, so rotating cannot repair it either — but the PR describes the set as "exact pre-connect DNS and network-reachability failures", and on Bun it is broader than that. Worth saying so in the code comment so the next reader does not assume the distinction holds.

Two cases my audit found that are worth adding to your test matrix, both of which put a rejection on this path after the credential was seen:

  1. Redirects. Bun follows them by default. A server can accept the authenticated request, return 307 to a dead host, and the wrapper rejects with ConnectionRefused — headers arrived, credential evaluated.
  2. Read-then-close. A server that reads Authorization and closes the socket without responding yields ECONNRESET. Your account-scoped set covers this one correctly, but the pair is worth pinning together so a later refactor cannot move ECONNRESET across the line by accident.

One more, from the same audit: a transient 5xx that exhausts fetchWithTransientRetry() surfaces as a rejected promise rather than a response, so "the promise rejected" and "no HTTP response arrived" are not the same condition on this path. Your classifier keys on error shape rather than on rejection, so I do not think it is affected — but it is the kind of thing that bites a later simplification.

The analysis that produced all of this is in devlog/_plan/260803_transport_attribution/000_plan.md on dev, including the three designs that failed and why. Your approach is a fourth one I had not considered, and the ConnectionRefused inclusion is what makes it viable where mine were not.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-site/src/content/docs/reference/configuration/providers.md`:
- Line 25: Update the upstreamFailoverThreshold documentation in
docs-site/src/content/docs/reference/configuration/providers.md:25-25,
docs-site/src/content/docs/ja/reference/configuration/providers.md:24-24,
docs-site/src/content/docs/ko/reference/configuration/providers.md:24-24,
docs-site/src/content/docs/ru/reference/configuration/providers.md:25-25, and
docs-site/src/content/docs/zh-cn/reference/configuration/providers.md:24-24 to
state that account-neutral pre-connect failures leave account quarantine,
cooldown, failure streak, thread affinity, rotation, and active-account
selection unchanged, while preserving the existing account-scoped transitions
for 401/403, 429, timeouts, resets, socket closures, HTTP failures, and semantic
upstream failures; provide equivalent wording in each locale.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 022e3281-6268-49d8-b09e-b0d908c5cdf4

📥 Commits

Reviewing files that changed from the base of the PR and between 888c955 and 399a991.

📒 Files selected for processing (20)
  • docs-site/src/content/docs/ja/reference/configuration/providers.md
  • docs-site/src/content/docs/ko/reference/configuration/providers.md
  • docs-site/src/content/docs/reference/configuration/providers.md
  • docs-site/src/content/docs/ru/reference/configuration/providers.md
  • docs-site/src/content/docs/zh-cn/reference/configuration/providers.md
  • src/lib/upstream-retry.ts
  • src/providers/openai-sidecar.ts
  • src/server/images.ts
  • src/server/index.ts
  • src/server/live.ts
  • src/server/responses/core.ts
  • src/server/search.ts
  • src/server/ws-bridge.ts
  • src/vision/describe.ts
  • src/vision/index.ts
  • src/web-search/executor.ts
  • src/web-search/loop.ts
  • tests/codex-account-neutral-network.test.ts
  • tests/codex-sidecar-turn-lease.test.ts
  • tests/sidecar-abort.test.ts

Comment thread docs-site/src/content/docs/reference/configuration/providers.md Outdated

@lidge-jun lidge-jun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this — the problem is real and your framing of it in #914 was accurate. But I have to ask for changes, and the reason is specific: this is the first of four designs that were already tried and rejected for this exact issue.

The full history is on dev at devlog/_plan/260803_transport_attribution/000_plan.md. Worth reading before the next revision, because it will save you the next two attempts as well.

The blocker

src/lib/upstream-retry.ts:32-43 allowlists Node codes plus Bun aliases including ConnectionRefused, and :140-182 walks the cause chain returning neutral on a match. That is "infer attribution from the rejected fetch's error code." Adding the Bun spellings makes the branch reachable — which the original design was not — but reachable is not correct.

A review probed Bun 1.3.14 against your classifier directly:

same .invalid host, 8 calls: ConnectionRefused → neutral=true
                             FailedToOpenSocket → neutral=false   (alternating)
refused port:                ConnectionRefused → neutral=true
server read Authorization: Bearer …, then 307 → refused port:
                             ConnectionRefused → neutral=true
first attempt 503, retry redirected to refusal:
                             ConnectionRefused → neutral=true

Three consequences:

  1. A real DNS outage still rotates the account. Bun evicts its DNS cache after a failure, so calls alternate between the two labels. Calls 2, 4, 6 return FailedToOpenSocket, are not neutral, accumulate, and trip the threshold. The exact symptom #914 reports survives.
  2. Credential-visible failures get suppressed. Bun follows redirects by default. A server that received your bearer and answered 307 to a dead host produces a final ConnectionRefused — your classifier calls that neutral, but a credential-aware upstream can behave differently for account A than for B. That is a genuine account signal being discarded.
  3. The 503 → rejection hole. fetchWithTransientRetry returns a transient 5xx only on the final attempt. An attributable 503 observed on attempt one vanishes when a later attempt rejects, and the rejection is then marked neutral.

Why the tests pass anyway

tests/codex-account-neutral-network.test.ts:153-256 injects hand-built errors — codedError("ENOTFOUND") and friends. That mirrors the allowlist rather than testing it. The suite is green because it asks the classifier the same question the classifier answers by construction; the runtime probe asks the question production asks, and gets a different answer.

This is not a criticism of your care — it is the specific trap this issue keeps setting, and the reason the recorded plan requires a runtime-grounded test against real Bun errors for any classifier here.

What the recorded conclusion suggests instead

Four designs died in the same place, which is evidence about the problem rather than about the designs. Every one tried to answer "was this the credential's fault?" from evidence that is insufficient in principle.

The direction the plan lands on is separating host health from account health: every pool account shares the host, so a network fault should mark the (provider, host) pair, not a credential, and rotation stops being the response at all. This PR does not do that — src/codex/routing.ts is untouched, there is no host key, threshold, or cooldown; it only skips account recording when the classifier fires.

If you want to take that on, it is genuinely valuable work and I would review it gladly.

Two smaller things

Scope. The sidecar quota-probe ownership work (OpenAiSidecarTurnLease, releaseProbeLease, the 387-line lifecycle suite) and the post-header outcome precedence changes in images.ts:445 / web-search/executor.ts:85 are separate policy questions — the latter is adjacent to #919, not required by #914. They would land more easily on their own.

Conflict. src/server/responses/compact.ts conflicts semantically, not textually: dev gained #913's bounded alternate-account send with per-context recording, and this PR edits the older single-account catch. Reconciling needs a decision about attribution for both contexts.

The documentation also states two things the implementation does not do: it calls the classified failures "pre-connect" (the rejection cannot prove that), and says aborts stay account-scoped while accountScopedTransportOutcome():198 makes them neutral. Same text in all four locales.

Leaving this open rather than closing it — the issue is real and you clearly have the context to solve it.

@Wibias
Wibias marked this pull request as draft August 3, 2026 20:49
@luvs01
luvs01 force-pushed the fix/914-account-neutral-network branch from 399a991 to 2b46044 Compare August 4, 2026 03:04
@luvs01 luvs01 changed the title fix(codex): keep shared network failures account-neutral fix(codex): isolate provider host transport health Aug 4, 2026
@luvs01
luvs01 marked this pull request as ready for review August 4, 2026 03:08
@luvs01

luvs01 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@lidge-jun @Wibias @Ingwannu This is now rewritten on current dev at 2b460446 using the requested provider + canonical-origin host-health design. The rejected error classifier and unrelated sidecar/post-header scope are removed; regular Responses and native compact preserve ordered physical-attempt evidence and the #927 retry invariants.

Fresh exact-head validation: focused runtime/routing tests 97/97 (887 assertions), host-only affinity contracts 3/3 (23 assertions), typecheck, privacy scan, diff check, and a 216-page docs build. Ready for re-review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-site/src/content/docs/reference/configuration/providers.md`:
- Line 25: Synchronize the upstreamFailoverThreshold contract across
docs-site/src/content/docs/reference/configuration/providers.md:25-25,
docs-site/src/content/docs/ja/reference/configuration/providers.md:24-24,
docs-site/src/content/docs/ko/reference/configuration/providers.md:24-24,
docs-site/src/content/docs/ru/reference/configuration/providers.md:25-25, and
docs-site/src/content/docs/zh-cn/reference/configuration/providers.md:24-24 with
runtime behavior: describe the bounded-cause-chain pre-connect classifier
including ConnectionRefused as host-neutral, keep read-then-close ECONNRESET
account-scoped when authenticated data may have been consumed, remove blanket
no-response wording, restrict host updates to exact classified outcomes, and
state that the 503 evidence rule applies only to a later host-neutral rejection.

In `@src/codex/upstream-host-health.ts`:
- Around line 90-119: Update oldestNonLeasedKey and its callers makeRoom and
pruneOverflow to accept the current now value and prefer eviction candidates
without an active cooldownUntil before comparing lastTouchedAt. Preserve leased
entries and the existing capacity-bound behavior, while retaining cooldown
entries whenever any non-cooldown, non-leased entry is available.
- Around line 235-258: Update recordCodexUpstreamHostFailure and the
corresponding reset/delete path to preserve the current generation and remaining
activeLeaseIds when resetting host health, removing only the completed lease
instead of replacing or deleting state needed by concurrent requests. Ensure
subsequent transport failures from remaining leases still match and record
evidence, and add a test covering an observed response followed by a concurrent
transport failure.

In `@src/server/responses/core.ts`:
- Around line 1792-1801: Release the auth-context probe lease for every
transport-only failure by changing the host-lease-guarded branches in
src/server/responses/core.ts:1792-1801 and
src/server/responses/compact.ts:476-485 to run releaseCodexAuthContextProbeLease
whenever observedStatus is undefined, regardless of host lease presence. Add a
focused regression test near the existing response subsystem tests covering a
pool-authenticated forward request with a null canonical host key and
pre-response rejection, asserting the account probe lease is free afterward.

In `@tests/codex-host-health-runtime.test.ts`:
- Around line 323-337: Replace the DNS-dependent invalidOrigin used by the
installCanonicalRouter callback with a deterministic refused loopback origin
from closedEphemeralPort(), matching the setup used by other tests in this file.
Keep the actualFetch error capture and existing circuit-breaker assertions
unchanged, including the distinction from canonicalHostKey.
- Around line 142-169: Add an `error` listener that ignores expected socket
errors to each tracked socket in the `createServer` connection callback,
alongside the existing `data` and `close` listeners. Ensure sockets destroyed by
the request handler or cleanup of `trackedSockets` cannot emit unhandled errors
during tests.

In `@tests/codex-upstream-host-health.test.ts`:
- Around line 36-45: Extend the existing normalized host-key test around
canonicalCodexUpstreamHostKey to cover trailing-dot removal, IPv6 bracket
normalization/round-tripping, and implicit :80 equivalence for HTTP URLs. Import
isCodexUpstreamRedirectStatus and add focused assertions covering its redirect
and non-redirect outcomes. Keep the tests adjacent to the existing upstream host
health cases.

In `@tests/issue-452-empty-503.test.ts`:
- Around line 194-205: Update the fetch interceptor in installCodexTransport to
be async, ensuring synchronous throws from send(init) become promise rejections
while preserving the existing routing behavior for non-Codex requests.
- Around line 261-276: Update the circuit-opening test around
sendRegularPoolRequest to import and use CODEX_UPSTREAM_HOST_FAILURE_THRESHOLD
for the failure loop bound and expected sends count, replacing both hardcoded 3
values so the test remains aligned with the exported threshold.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3a99c21e-814f-4718-980d-8293077aa951

📥 Commits

Reviewing files that changed from the base of the PR and between 399a991 and 2b46044.

📒 Files selected for processing (12)
  • docs-site/src/content/docs/ja/reference/configuration/providers.md
  • docs-site/src/content/docs/ko/reference/configuration/providers.md
  • docs-site/src/content/docs/reference/configuration/providers.md
  • docs-site/src/content/docs/ru/reference/configuration/providers.md
  • docs-site/src/content/docs/zh-cn/reference/configuration/providers.md
  • src/codex/upstream-host-health.ts
  • src/lib/upstream-retry.ts
  • src/server/responses/compact.ts
  • src/server/responses/core.ts
  • tests/codex-host-health-runtime.test.ts
  • tests/codex-upstream-host-health.test.ts
  • tests/issue-452-empty-503.test.ts

Comment thread docs-site/src/content/docs/reference/configuration/providers.md Outdated
Comment thread src/codex/upstream-host-health.ts Outdated
Comment thread src/codex/upstream-host-health.ts
Comment thread src/server/responses/core.ts
Comment thread tests/codex-host-health-runtime.test.ts
Comment thread tests/codex-host-health-runtime.test.ts
Comment thread tests/codex-upstream-host-health.test.ts
Comment thread tests/issue-452-empty-503.test.ts
Comment thread tests/issue-452-empty-503.test.ts
@luvs01
luvs01 force-pushed the fix/914-account-neutral-network branch from 2b46044 to 92dd06c Compare August 4, 2026 04:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (1)
tests/codex-host-health-runtime.test.ts (1)

326-336: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The circuit test still depends on DNS resolution of a .invalid name.

Line 326 keeps http://same-host-health-target.invalid. A resolver that synthesizes wildcard A records for NXDOMAIN returns an address, actualFetch at line 331 then returns an HTTP response, and no host failure is recorded. Lines 344-347 and 352 then fail with no indication that DNS caused the failure. Every other test in this file uses closedEphemeralPort(), which refuses deterministically without DNS.

Use a refused loopback origin here too. The refused loopback key still differs from canonicalHostKey, so lines 353-355 remain valid.

♻️ Proposed change to a deterministic refused origin
-    const invalidOrigin = "http://same-host-health-target.invalid";
+    const refusedPort = await closedEphemeralPort();
+    const invalidOrigin = `http://127.0.0.1:${refusedPort}`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex-host-health-runtime.test.ts` around lines 326 - 336, Replace the
DNS-dependent invalidOrigin in the circuit test with a refused loopback origin
obtained from closedEphemeralPort(), matching the deterministic pattern used
elsewhere in the file. Keep the resulting host key distinct from
canonicalHostKey so the existing assertions around lines 353-355 remain valid.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-site/src/content/docs/reference/configuration/providers.md`:
- Line 25: Update the upstreamFailoverThreshold documentation to state that
thresholded account failures can trigger failover for subsequent requests,
including requests from existing bound tasks, by clearing thread affinity and
changing active-account routing; do not limit the behavior to future new
sessions. Apply the corresponding translated wording in
docs-site/src/content/docs/ja/reference/configuration/providers.md:24,
docs-site/src/content/docs/ko/reference/configuration/providers.md:24,
docs-site/src/content/docs/ru/reference/configuration/providers.md:25, and
docs-site/src/content/docs/zh-cn/reference/configuration/providers.md:24, while
updating docs-site/src/content/docs/reference/configuration/providers.md:25.

In `@src/server/responses/core.ts`:
- Around line 1860-1873: Both retry paths incorrectly use per-attempt executor
flags to classify failures across the entire retry ladder. In
src/server/responses/core.ts lines 1860-1873, require attemptHistory.length ===
0 alongside !primaryAttemptExecutorStarted; in src/server/responses/compact.ts
lines 520-540, require primaryAttempts.length === 0 alongside
!primaryAttemptBoundary.executorStarted. Add a focused regression test near the
existing response subsystem tests that rejects the first physical attempt, fails
before executor start on retry, and verifies host health records the failure.
- Around line 459-472: Use the same hostKey-based condition for the
alternate-account request’s redirect mode and its redirect-to-502 guard. Thread
the caller’s boolean through CodexPoolAccountRetryArgs, apply it when setting
redirect in the alternate send, and reuse it in the guard so uncanonicalizable
upstreams continue following redirects as before.

In `@tests/codex-host-health-runtime.test.ts`:
- Around line 120-133: The tests must bind their server fixtures before
reserving the refused port so those servers cannot reclaim it. Update the setup
around the tests using closedEphemeralPort() to start each serve(..., port: 0)
fixture first, then call closedEphemeralPort(), preserving the intended
connection-rejection assertions.

In `@tests/responses-compaction-routing.test.ts`:
- Around line 1675-1694: Update the test around the “an open compact host
circuit returns Retry-After without another send” case to use the imported
CODEX_UPSTREAM_HOST_FAILURE_THRESHOLD for both the failure-attempt loop bound
and the expected sends count, replacing the hardcoded 3 while preserving the
existing blocked-request assertions.
- Around line 1141-1190: Update the test containing “compact settles A's
half-open account probe when response header processing throws” to isolate
credential persistence using the existing temporary-home pattern from
neighboring tests, such as withPoolEnv or explicit OPENCODEX_HOME/CODEX_HOME
setup and restoration. Ensure saveCodexAccountCredential writes only to the
temporary directory while preserving the test’s current assertions and cleanup.
- Around line 772-802: Update throwingPreExecutorHeaders to define a
BOUNDARY_FRAME constant and track whether the fetchWithHeaderTimeout stack frame
is observed during header cloning. Assert that the frame was observed before the
affected tests assert rejects.toBe(expected), so stack-format or
function-renaming changes fail with an explicit contract error rather than a
misleading rejection failure.

In `@tests/server-auth.test.ts`:
- Around line 2604-2630: Introduce a descriptive constant near the response
proxy setup for the expected pre-alternate-preparation response.headers read
count, documenting that it mirrors the access sequence in core.ts. Use this
constant for both the throw condition and the headerReads assertion, preserving
the existing value and test behavior while making the coupling explicit.

---

Duplicate comments:
In `@tests/codex-host-health-runtime.test.ts`:
- Around line 326-336: Replace the DNS-dependent invalidOrigin in the circuit
test with a refused loopback origin obtained from closedEphemeralPort(),
matching the deterministic pattern used elsewhere in the file. Keep the
resulting host key distinct from canonicalHostKey so the existing assertions
around lines 353-355 remain valid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 255e1afd-d815-47da-9a53-20ebe46b00b5

📥 Commits

Reviewing files that changed from the base of the PR and between 2b46044 and 92dd06c.

📒 Files selected for processing (15)
  • docs-site/src/content/docs/ja/reference/configuration/providers.md
  • docs-site/src/content/docs/ko/reference/configuration/providers.md
  • docs-site/src/content/docs/reference/configuration/providers.md
  • docs-site/src/content/docs/ru/reference/configuration/providers.md
  • docs-site/src/content/docs/zh-cn/reference/configuration/providers.md
  • src/codex/upstream-host-health.ts
  • src/lib/upstream-retry.ts
  • src/server/responses/compact.ts
  • src/server/responses/core.ts
  • tests/codex-host-health-runtime.test.ts
  • tests/codex-upstream-host-health.test.ts
  • tests/issue-452-empty-503.test.ts
  • tests/responses-compaction-routing.test.ts
  • tests/server-auth.test.ts
  • tests/upstream-transient-retry.test.ts

Comment thread docs-site/src/content/docs/reference/configuration/providers.md Outdated
Comment thread src/server/responses/core.ts
Comment thread src/server/responses/core.ts
Comment thread tests/codex-host-health-runtime.test.ts
Comment thread tests/responses-compaction-routing.test.ts
Comment thread tests/responses-compaction-routing.test.ts
Comment thread tests/responses-compaction-routing.test.ts
Comment thread tests/server-auth.test.ts
@luvs01

luvs01 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Updated comparison with #966 for current #922 head d6c37343.

Both proposals share the important invariants: shared reachability failures must not penalize or rotate a pool account; credential-bearing redirects must not be followed automatically; prior HTTP evidence must survive a later rejection; discarded retry bodies must be cancelled; and probe settlement must respect lease ownership.

#922 explicitly credits and incorporates the strongest evidence from #966:

  • the real Bun 1.3.14 ConnectionRefused / FailedToOpenSocket runtime probes, which rule out a Node-only taxonomy
  • preservation of transient-status evidence together with best-effort response-body cancellation
  • the credential-bearing redirect-path inventory
  • exact-account sidecar metadata as a useful boundary check, while sidecars remain outside this PR
  • negative ownership/account-state tests: release only the owned probe and leave streak, cooldown, affinity, and active selection unchanged for host-only failures

The remaining differences are intentional:

#966 is currently an independent draft at 71bd8210. This note is not a dismissal of that work: it records what #922 accepted from it and makes the remaining enforcement, attribution, redirect, and scope choices explicit for maintainers.

@Wibias

Wibias commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

[GD] Verdict: changes-requested

TLDR

  • PR: #922 — fix(codex): isolate provider host transport health
  • Head: 92dd06cd on dev (mergeStateStatus: UNSTABLE)
  • Decision: useful redesign for #914, but not merge-ready; concrete blockers remain for @luvs01
  • Usefulness: real account-pool bug; host circuit is the accepted direction after the Bun classifier failures
  • Bugs: pre-exec classification can drop a prior physical rejection from host evidence; docs understate affinity/active failover; one compact probe test writes credentials outside a temp home
  • Security: Pass (manual redirect + no credential replay look sound); residual: open host circuit intentionally blocks later logical requests including alternates
  • Spec / standards: core #914 host/account split is implemented for regular + compact; docs still say “future new sessions” while runtime can clear existing affinity/active account
  • Reviews: owner CHANGES_REQUESTED still open on the old classifier design; 8 unresolved CodeRabbit threads; several nits, two majors still valid
  • Base / CI: diverged from current dev (behind_by: 10); fork CI is action_required for Cross-platform CI + React Doctor
  • Gate: ship-gate blocked on review policy + unresolved threads + trusted-human wake items
  • Owner actions: update from latest dev; fix the blockers below; re-request review; approve fork CI; optional simplify candidates listed in full verdict
  • Bottom line: keep this design over classifier-only observation, but request changes before ship
Full verdict

Semantic propagation

  • Concepts audited: host transport health key; generation-fenced host admission/circuit; account vs host failure attribution; ordered physical-attempt evidence; manual credential-bearing redirects; probe-lease release; regular Responses vs native compact parity
  • Authoritative sources: src/codex/upstream-host-health.ts; src/codex/routing.ts recordCodexUpstreamOutcome; src/server/responses/core.ts / compact.ts; issue #914 + owner plan notes
  • Producers and consumers checked: primary/alternate regular sends; compact primary/alternate; host admission acquire/release/failure/response; account outcome recording; docs locales
  • Public/derived representations checked: client 502 + Retry-After; providers.md EN/JA/KO/RU/ZH-CN; no new public management API surface
  • Material variant partitions checked: regular vs compact; primary vs alternate; response-then-reject vs bare reject; half-open success/fail/stale generation; timeout/refused/.invalid runtime; fixed-account no-alternate
  • Positive and negative assertions checked: host-only leaves account streak/affinity/active untouched; observed HTTP remains account evidence; no Location exposure; no cross-account replay after credential-visible failure
  • Unmapped surfaces: sidecars/search/images intentionally out of scope (documented vs #966)
  • Unproven equivalence assumptions: none material inside claimed scope
  • Representation mismatches: docs “future new sessions” vs runtime affinity clear / active failover
  • Variant coverage gaps: retry ladder path where physical rejection is followed by pre-executor setup failure is under-specified by tests
  • Axis verdict: blocked

Linked: #914 (body says Fixes #914); related #919 / #915 / #966

Usefulness

Useful. #914 is real: pre-response transport failures were account-attributed, so shared DNS/network faults soft-avoided working pool accounts. This head implements the host-ledger direction the owner later endorsed, with generation-fenced half-open admission and ordered attempt evidence. Better than the earlier classifier-only attempt.

Bugs / correctness

  • Method: bug-review.md — Bugbot: n/a-unavailable on Codex; complementary: done (silent_failures / resource_leaks / edge_cases + retry/probe lenses)
  • Findings:
    1. Confirmed Medium/High — pre-exec catch ignores prior physical rejections. In src/server/responses/core.ts (~1860) and the compact twin (~520), each physical attempt resets primaryAttemptExecutorStarted / executorStarted. onAttempt can already have stored a prior rejection, but the outer catch still treats !executorStarted as pure local setup failure when lastUpstreamAttemptResponseStatus(history) is empty. Result: a real first-attempt send that rejected, followed by retry setup failure, releases the host lease without recording host failure. Covered case is only prior HTTP status (503), not prior rejection. Fix: require empty attempt history (or any observed physical attempt) before the local-setup branch; add regression for reject-then-pre-exec.
    2. Confirmed Medium — docs understate failover blast radius. providers.md still says account threshold affects “future new sessions”, but recordCodexUpstreamOutcome can soft-avoid, clear affinity for existing threads, and promote active account. Same wording is mirrored in JA/KO/RU/ZH-CN. This is a contract mismatch in the PR’s rewritten paragraph.
    3. Confirmed Medium — test credential isolation gap. tests/responses-compaction-routing.test.ts “compact settles A's half-open account probe when response header processing throws” calls saveCodexAccountCredential without the neighboring withPoolEnv / temp-home pattern, so it can write into ambient OPENCODEX_HOME / CODEX_HOME.
    4. Residual / low: alternate path hardcodes redirect: "manual" while primary gates on hostKey; for normal Codex pool URLs hostKey exists whenever alternate runs, so this is consistency hardening more than a production break.
  • Fixed this session: none (foreign PR; no push)

Security

  • Scope reviewed: outbound/manual redirects, credential replay boundary, probe-lease lifecycle, logging/privacy of attempt observations, AI-proxy defensive surface (no new tool/MCP install path)
  • Decision: Pass
  • Risk: Low–Medium residual availability only
  • Findings: none confirmed exploitable
  • Notes: manual redirect + no Location exposure is correct; not replaying after a credential-bearing rejection is correct; open host circuit intentionally blocks later logical requests to the same origin (including healthy alternates) — documented behavior, not an authz bypass
  • Fixed this session: none
  • Adversarial/red-team second pass: not requested, skipped

Spec / standards

  • Spec source: issue #914 + PR body + owner transport-attribution plan
  • Spec gaps: docs affinity/active-account wording; reject-then-pre-exec evidence path not pinned
  • Standards: Bun-native tests are present; privacy-safe attempt observations look good; no drive-by scope explosion inside claimed regular/compact boundary
  • Advisory smells: duplicated admission/settlement glue across core/compact (acceptable for now); magic 3 / 5 in tests

Reviews

  • Owners/maintainers: CHANGES_REQUESTED by @lidge-jun on the old Node-code classifier design; later host-health rewrite addresses that root objection, but the review state is still open and must be re-requested after fixes
  • Owner issue comment about Bun ConnectionRefused breadth is largely superseded by the host-ledger design; still worth one explicit comment that refused-port and NXDOMAIN share Bun labels and are intentionally host-scoped
  • Bots: CodeRabbit success summary, 8 unresolved threads. Valid majors: docs failover wording; pre-exec classification; credential-home isolation. Acceptable nits: .invalid DNS flakiness (other tests already use refused ports), stack-frame contract, literal 3/5 naming

Base / CI

  • Behind/conflicts: owner action: update from latest dev — compare reports status: diverged, behind_by: 10, ahead_by: 10 (merge-base 5d2973c; current tip moved)
  • Required checks helper: ready (no enforced matrix names), but live fork workflows for this head are Cross-platform CI + React Doctor = action_required (maintainer approval), not a green test matrix
  • Local tip evidence on 92dd06cd / Bun 1.3.14:
    • bun test tests/codex-upstream-host-health.test.ts → 14 pass
    • bun test tests/upstream-transient-retry.test.ts → 10 pass
    • bun test tests/codex-host-health-runtime.test.ts → 8 pass
    • bun test tests/responses-compaction-routing.test.ts → 52 pass
    • bun test tests/server-auth.test.ts → 73 pass
    • bun run typecheck → pass
    • tests/issue-452-empty-503.test.ts was environment-blocked here by Windows EBUSY on a locked temp dir (not treated as a product regression)

Simplification (for the PR owner)

Foreign PR: candidates only; nothing edited or pushed.

  1. tests/responses-compaction-routing.test.ts open-circuit test — replace literal 3 with CODEX_UPSTREAM_HOST_FAILURE_THRESHOLD (already imported). Risk: low. Validate: that one test.
  2. tests/server-auth.test.ts alternate-preparation proxy — name the header-read threshold constant and comment the core.ts coupling. Risk: low. Validate: that one test.
  3. Optional only after bug docs: record OpenCode Go official contract retry #1 fix: one tiny shared helper for “local pre-executor failure vs physical attempt already observed” used by regular + compact, to keep the two catch paths from drifting again. Risk: medium; do not extract if it obscures the settlement rules.

No broad dedup of core/compact settlement is recommended in this PR.

Gate

  • ship-gate.mjs (mutation maintainer, workflow full-review-pr): blocked
  • Blockers: reviewPolicy:changes_requested; reviewThreads:unresolved_review_threads; trusted-human wake on issue comment 5162870843 and review 4841913260
  • Draft/WIP: none

Bottom line

Ship direction is right: isolate shared host reachability from account health, fence half-open admission, preserve ordered evidence, keep redirects manual for pooled Codex sends. Do not merge yet. @luvs01 should rebase/update onto current dev, fix the pre-exec history bug + docs + temp-home test, clear/reply the still-valid review threads, get fork CI approved and green, then re-request maintainer review. Optional simplify items above are owner-side only.

@Wibias
Wibias marked this pull request as draft August 4, 2026 07:30
@luvs01
luvs01 force-pushed the fix/914-account-neutral-network branch from 92dd06c to d6c3734 Compare August 4, 2026 11:50

luvs01 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@Wibias Current head d6c373439e3c12ad595cfb14e70e609419695980 is ready for the CI/Ready check.

  • rebased without patch drift onto current dev e44d234f; GitHub reports the PR mergeable
  • fixed the retry-history, five-locale failover wording, temporary credential-home isolation, redirect/null-host parity, fixture, and test-contract findings
  • replied to and resolved all eight current CodeRabbit threads; unresolved review threads are now 0
  • Bun 1.3.14 focused host/routing and account-catalog contracts are functionally green after isolating this Windows host's 5-second timing cases
  • typecheck, privacy scan, diff check, 216-page docs build, range-diff, and two independent semantic reviews passed with no P0-P3 findings

Cross-platform CI and React Doctor are currently action_required, awaiting maintainer approval for the fork revision. Could you approve those workflows and confirm whether this revision may move through the repository's Ready checklist once they are green?

@luvs01
luvs01 requested a review from lidge-jun August 4, 2026 12:14
@luvs01
luvs01 marked this pull request as ready for review August 4, 2026 12:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6c373439e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

| `accountPoolStrategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Assignment strategy for new/unbound Codex requests. A request is unbound when it has no live (parent thread id, quota scope) affinity; a visible existing task can become unbound after proxy restart or affinity reset. `quota` picks the lowest-usage eligible account when no active account exists, keeps an eligible active account below `autoSwitchThreshold`, and after the threshold may move an unbound request or proactively rebind a bound task to a lower-usage eligible account. `round-robin` distributes unbound requests evenly; `fill-first` keeps assigning unbound requests to the active account until cooldown, unavailability, or the configured drain threshold. |
| `accountPoolStickyLimit?` | `number` | `1` | New/unbound task assignments retained on one round-robin selection before advancing; the counter advances when a task is bound, not after an upstream success. Range 1–100. |
| `upstreamFailoverThreshold?` | `number` | `3` | Consecutive transient failures before future new sessions fail over. Set `0` to disable. |
| `upstreamFailoverThreshold?` | `number` | `3` | Consecutive account-scoped transient HTTP or semantic failure outcomes required before subsequent requests, including existing bound tasks, may fail over; `0` disables only account failover. A terminal logical request that rejects before any HTTP status is observed updates only process-local host health keyed by `(provider, canonical HTTP(S) origin)` and releases any account probe lease; it does not change account quarantine, account cooldown/failure streak, affinity, pool rotation, or active account selection. Three terminal logical host failures within five minutes open the host circuit for 30 seconds; then exactly one half-open logical request is admitted while concurrent requests remain blocked. An observed HTTP failure status remains account evidence. If a `503` response is followed by a rejection, ordered evidence retains the `503` for the account and the later rejection for the host. Codex bearer redirects for pooled regular Responses and native compact requests are handled manually: they are not followed, `Location` is not exposed, and they become a bounded account-scoped `502`. If the peer may have consumed a credential-bearing request but the transport rejects before any HTTP status is observed, the request is not replayed under another credential; that terminal rejection can contribute to a host circuit that temporarily blocks an otherwise healthy alternate. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope the account-neutral guarantee to covered request paths

When a pooled OpenAI vision or web-search sidecar rejects before returning an HTTP status, this unconditional guarantee is still false: src/web-search/executor.ts:96-98 and src/vision/describe.ts:116-118 report connect_error/timeout, and src/providers/openai-sidecar.ts:160-168 forwards that outcome to recordCodexUpstreamOutcome, which increments the selected account's transient-failure streak and can clear affinity or rotate it. Fresh evidence in the reviewed tree is that these catches and callbacks remain unchanged despite the earlier thread saying they were addressed. Apply the host-only classifier and probe-lease settlement to these pool-backed sidecars, or explicitly limit the documentation to pooled regular Responses and native compact requests.

AGENTS.md reference: docs-site/AGENTS.md:L7-L10

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-site/src/content/docs/reference/configuration/providers.md`:
- Line 25: Update the upstreamFailoverThreshold documentation to describe
host-only attribution only for exact bounded-chain pre-connect DNS/reachability
failures, including ConnectionRefused for nonexistent hosts and refused ports.
Explicitly keep timeouts, aborts, ECONNRESET, socket closures, HTTP failures,
semantic failures, and read-then-close ECONNRESET account-scoped, while
retaining no-replay credential protection and the rule that 503 evidence is
followed only by a later host-neutral rejection. Apply synchronized wording in
docs-site/src/content/docs/reference/configuration/providers.md:25-25,
docs-site/src/content/docs/ja/reference/configuration/providers.md:24-24,
docs-site/src/content/docs/ko/reference/configuration/providers.md:24-24,
docs-site/src/content/docs/ru/reference/configuration/providers.md:25-25, and
docs-site/src/content/docs/zh-cn/reference/configuration/providers.md:24-24,
translating the classifier and negative cases appropriately.

In `@tests/codex-host-health-runtime.test.ts`:
- Around line 349-351: Replace the vacuous runtimeErrorLabel/every assertion
with an assertion that runtimeErrors.length equals physicalSends.length (using
the existing threshold relationship) in the test around physicalSends. Preserve
the label-shape check only if needed for its documented tolerance, and ensure
the rejection-count assertion identifies missing resolver rejection before the
cooldown assertion.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 23584a92-0d4e-44c9-8477-7ea20ff821fb

📥 Commits

Reviewing files that changed from the base of the PR and between 92dd06c and d6c3734.

📒 Files selected for processing (17)
  • docs-site/src/content/docs/ja/reference/configuration/providers.md
  • docs-site/src/content/docs/ko/reference/configuration/providers.md
  • docs-site/src/content/docs/reference/configuration/providers.md
  • docs-site/src/content/docs/ru/reference/configuration/providers.md
  • docs-site/src/content/docs/zh-cn/reference/configuration/providers.md
  • src/codex/routing.ts
  • src/codex/upstream-host-health.ts
  • src/lib/upstream-retry.ts
  • src/server/responses/compact.ts
  • src/server/responses/core.ts
  • tests/codex-host-health-runtime.test.ts
  • tests/codex-upstream-host-health.test.ts
  • tests/helpers/isolated-codex-home.ts
  • tests/issue-452-empty-503.test.ts
  • tests/responses-compaction-routing.test.ts
  • tests/server-auth.test.ts
  • tests/upstream-transient-retry.test.ts

| `accountPoolStrategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Assignment strategy for new/unbound Codex requests. A request is unbound when it has no live (parent thread id, quota scope) affinity; a visible existing task can become unbound after proxy restart or affinity reset. `quota` picks the lowest-usage eligible account when no active account exists, keeps an eligible active account below `autoSwitchThreshold`, and after the threshold may move an unbound request or proactively rebind a bound task to a lower-usage eligible account. `round-robin` distributes unbound requests evenly; `fill-first` keeps assigning unbound requests to the active account until cooldown, unavailability, or the configured drain threshold. |
| `accountPoolStickyLimit?` | `number` | `1` | New/unbound task assignments retained on one round-robin selection before advancing; the counter advances when a task is bound, not after an upstream success. Range 1–100. |
| `upstreamFailoverThreshold?` | `number` | `3` | Consecutive transient failures before future new sessions fail over. Set `0` to disable. |
| `upstreamFailoverThreshold?` | `number` | `3` | Consecutive account-scoped transient HTTP or semantic failure outcomes required before subsequent requests, including existing bound tasks, may fail over; `0` disables only account failover. A terminal logical request that rejects before any HTTP status is observed updates only process-local host health keyed by `(provider, canonical HTTP(S) origin)` and releases any account probe lease; it does not change account quarantine, account cooldown/failure streak, affinity, pool rotation, or active account selection. Three terminal logical host failures within five minutes open the host circuit for 30 seconds; then exactly one half-open logical request is admitted while concurrent requests remain blocked. An observed HTTP failure status remains account evidence. If a `503` response is followed by a rejection, ordered evidence retains the `503` for the account and the later rejection for the host. Codex bearer redirects for pooled regular Responses and native compact requests are handled manually: they are not followed, `Location` is not exposed, and they become a bounded account-scoped `502`. If the peer may have consumed a credential-bearing request but the transport rejects before any HTTP status is observed, the request is not replayed under another credential; that terminal rejection can contribute to a host circuit that temporarily blocks an otherwise healthy alternate. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use the exact runtime classifier as the account-neutral boundary.

All five entries classify any terminal rejection before an HTTP status as host-only. Runtime behavior limits host-neutral handling to exact pre-connect DNS/reachability failures found within a bounded cause chain, including ConnectionRefused for nonexistent hosts and refused ports. Timeouts, aborts, ECONNRESET, socket closures, HTTP failures, and semantic failures remain account-scoped. A read-then-close ECONNRESET remains account-scoped when authenticated data may have been consumed. The no-replay control protects credentials but does not change failure attribution. Keep the 503 rule limited to a later host-neutral rejection.

  • docs-site/src/content/docs/reference/configuration/providers.md#L25-L25: update the canonical English wording with the exact pre-connect classifier and account-scoped negative cases.
  • docs-site/src/content/docs/ja/reference/configuration/providers.md#L24-L24: translate the same classifier and negative cases into Japanese.
  • docs-site/src/content/docs/ko/reference/configuration/providers.md#L24-L24: translate the same classifier and negative cases into Korean.
  • docs-site/src/content/docs/ru/reference/configuration/providers.md#L25-L25: translate the same classifier and negative cases into Russian.
  • docs-site/src/content/docs/zh-cn/reference/configuration/providers.md#L24-L24: translate the same classifier and negative cases into Simplified Chinese.

As per path instructions, keep the English provider contract and all translated locale pages synchronized with actual runtime behavior.

📍 Affects 5 files
  • docs-site/src/content/docs/reference/configuration/providers.md#L25-L25 (this comment)
  • docs-site/src/content/docs/ja/reference/configuration/providers.md#L24-L24
  • docs-site/src/content/docs/ko/reference/configuration/providers.md#L24-L24
  • docs-site/src/content/docs/ru/reference/configuration/providers.md#L25-L25
  • docs-site/src/content/docs/zh-cn/reference/configuration/providers.md#L24-L24
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs-site/src/content/docs/reference/configuration/providers.md` at line 25,
Update the upstreamFailoverThreshold documentation to describe host-only
attribution only for exact bounded-chain pre-connect DNS/reachability failures,
including ConnectionRefused for nonexistent hosts and refused ports. Explicitly
keep timeouts, aborts, ECONNRESET, socket closures, HTTP failures, semantic
failures, and read-then-close ECONNRESET account-scoped, while retaining
no-replay credential protection and the rule that 503 evidence is followed only
by a later host-neutral rejection. Apply synchronized wording in
docs-site/src/content/docs/reference/configuration/providers.md:25-25,
docs-site/src/content/docs/ja/reference/configuration/providers.md:24-24,
docs-site/src/content/docs/ko/reference/configuration/providers.md:24-24,
docs-site/src/content/docs/ru/reference/configuration/providers.md:25-25, and
docs-site/src/content/docs/zh-cn/reference/configuration/providers.md:24-24,
translating the classifier and negative cases appropriately.

Source: Path instructions

Comment on lines +349 to +351
// Bun 1.3.14 on Windows has emitted more than one label for this same target.
// Activation correctness intentionally depends only on real rejection count.
expect(runtimeErrors.map(runtimeErrorLabel).every(label => label.length > 0)).toBe(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This assertion cannot fail, so it does not protect the .invalid fixture.

Line 351 maps runtimeErrors through runtimeErrorLabel and asserts every label has a non-zero length. Two properties make the check vacuous. Array.prototype.every returns true for an empty array, so an empty runtimeErrors passes. runtimeErrorLabel always returns at least "::" from the template on line 278, so any element passes too.

The failure mode this matters for is the one the retained .invalid fixture exists to exercise. If a resolver synthesizes an address for same-host-health-target.invalid, actualFetch at line 331 resolves, runtimeErrors stays empty, and line 351 still reports success. The test then fails later at line 352 with a missing cooldown, which does not name the resolution as the cause.

Assert the rejection count instead. physicalSends already equals the threshold at line 347, so every physical send must have rejected for the circuit to open.

♻️ Proposed change to make the rejection count the contract
     // Bun 1.3.14 on Windows has emitted more than one label for this same target.
     // Activation correctness intentionally depends only on real rejection count.
-    expect(runtimeErrors.map(runtimeErrorLabel).every(label => label.length > 0)).toBe(true);
+    expect(runtimeErrors).toHaveLength(CODEX_UPSTREAM_HOST_FAILURE_THRESHOLD);
+    expect(runtimeErrors.map(runtimeErrorLabel).every(label => label.length > 0)).toBe(true);

This keeps the label-shape tolerance the comment describes, and it names the resolution failure at the line that observes it.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Bun 1.3.14 on Windows has emitted more than one label for this same target.
// Activation correctness intentionally depends only on real rejection count.
expect(runtimeErrors.map(runtimeErrorLabel).every(label => label.length > 0)).toBe(true);
// Bun 1.3.14 on Windows has emitted more than one label for this same target.
// Activation correctness intentionally depends only on real rejection count.
expect(runtimeErrors).toHaveLength(CODEX_UPSTREAM_HOST_FAILURE_THRESHOLD);
expect(runtimeErrors.map(runtimeErrorLabel).every(label => label.length > 0)).toBe(true);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex-host-health-runtime.test.ts` around lines 349 - 351, Replace the
vacuous runtimeErrorLabel/every assertion with an assertion that
runtimeErrors.length equals physicalSends.length (using the existing threshold
relationship) in the test around physicalSends. Preserve the label-shape check
only if needed for its documented tolerance, and ensure the rejection-count
assertion identifies missing resolver rejection before the cooldown assertion.

Source: Learnings

@Wibias

Wibias commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

[GD] Verdict: changes-requested

TLDR

  • PR: #922 — fix(codex): isolate provider host transport health
  • Head: d6c37343 on dev (mergeStateStatus: UNSTABLE, mergeable; base tip e44d234f is the merge-base, so the rebase is current)
  • Decision: useful, correct direction, but not merge-ready; concrete blockers remain for @luvs01
  • Usefulness: real account-pool bug (#914); host/account separation is the owner-endorsed design and is implemented for regular Responses + native compact with ordered attempt evidence
  • Bugs: no code-behavior blocker confirmed. Docs overclaim the host-only guarantee across all five locales (sidecar paths still record account evidence); one vacuous test assertion in tests/codex-host-health-runtime.test.ts:351; one CodeRabbit thread is factually stale for this head (no classifier exists anymore)
  • Security: Pass — manual redirects, no Location exposure, no credential replay, fail-closed 502 + Retry-After, generation-fenced leases; residual availability tradeoff is documented
  • Spec / standards: core #914 contract implemented; docs-scope mismatch (sidecars) + issue-text deviation on TimeoutError/ECONNRESET attribution that should be stated in the PR body
  • Reviews: @lidge-jun CHANGES_REQUESTED (review 4841913260) is still formally in force, though it targets the old classifier head; 3 unresolved threads on this head (2 valid, 1 stale)
  • Base / CI: rebase current on dev@e44d234f; enforce-target + label green; Cross-platform CI (run 2877) and React Doctor (run 2024) are action_required — the real matrix has never run on this head
  • Gate: ship-gate blocked on reviewPolicy:changes_requested, reviewThreads:unresolved_review_threads, wake (issue comment 5162870843, review 4841913260)
  • Owner actions (foreign PR): scope the five-locale docs guarantee to covered paths; fix the vacuous assertion; reply/decline the stale CodeRabbit thread with runtime evidence; get fork CI approved and green; re-request maintainer review; optional simplify candidates listed below
  • Bottom line: keep this design — the previous full-review blockers are verifiably fixed on this head. Request changes for the docs-contract scope, the test assertion, thread/CI/review state, then re-review.
Full verdict

Semantic propagation

  • Concepts audited: host transport-health ledger (key, circuit, cooldown, half-open admission); account vs host failure attribution; ordered physical-attempt evidence; manual redirect policy; no-replay boundary; probe-lease lifecycle; alternate-outcome promotion suppression
  • Authoritative sources: src/codex/upstream-host-health.ts (ledger), src/codex/routing.ts recordCodexUpstreamOutcome / classifyCodexUpstreamOutcome, src/server/responses/core.ts + compact.ts settlement paths, docs-site/.../providers.md x 5 locales, issue #914 + devlog/_plan/260803_transport_attribution/000_plan.md
  • Producers and consumers checked: primary/alternate regular sends; compact primary/alternate; pre-auth vs post-auth admission; fixed-account 401 precedence; recordCodexUpstreamHostFailure/Response; lastUpstreamAttemptResponseStatus; routing streak/affinity/active-selection consumers; sidecar recordOutcome consumers (src/providers/openai-sidecar.ts, src/web-search/executor.ts, src/vision/describe.ts)
  • Public/derived representations checked: client-visible 502 + Retry-After; five-locale upstreamFailoverThreshold docs; no new management API surface
  • Material variant partitions checked: regular vs compact; primary vs alternate; half-open success/failure/stale generation; observed-response-then-rejection; pure pre-executor failure; reject-then-pre-exec; null canonical host key (native redirect fallback); fixed-account late admission; client abort
  • Positive and negative assertions checked: host-only failures leave account streak/affinity/active untouched (expectHostOnlyState); observed HTTP stays account evidence; no Location exposure; no cross-account replay after credential-visible failure; exactly one half-open logical request; stale leases cannot settle a newer generation
  • Unmapped surfaces: sidecar/search/image pre-status paths — deliberately out of scope per PR body, but the docs sentence does not say so (blocker)
  • Unproven equivalence assumptions: none material inside the claimed regular/compact scope
  • Representation mismatches: docs line 25 (all 5 locales) states any terminal pre-status rejection updates only host health; pool-backed sidecar requests still record connect_error/timeout against the selected account, so the unconditional sentence is false for them
  • Variant coverage gaps: none found inside the claimed scope; the 503 -> rejection and reject-then-pre-exec ladders are now pinned in both flows
  • Axis verdict: blocked (docs representation mismatch carried into this verdict)

Linked: #914 (Fixes), related #919, #915, #927, #966 (draft alternative, intentional differences recorded in the PR comparison comment)

Usefulness

Useful. #914 is real: pre-status transport failures were account-attributed, so shared DNS/network faults soft-avoided healthy pool accounts and rotated the active account. This head implements the owner-endorsed host-ledger direction (devlog/_plan/260803_transport_attribution/000_plan.md), with generation-fenced half-open admission, bounded cooldown, ordered attempt evidence, and manual redirects. The previous full-review blockers for head 92dd06cd are verifiably addressed here.

Bugs / correctness

  • Method: bug-review.md — Bugbot: n/a (Codex host, unavailable by design); static: bun run typecheck pass + full changed-file suites; complementary: done (silent_failures, resource_leaks, edge_cases, error_propagation, network_cancellation, retry_idempotency, concurrency_races, time_clocks, state_consistency)
  • Local evidence on exact head d6c37343 (Bun 1.3.14): tests/codex-upstream-host-health.test.ts, tests/upstream-transient-retry.test.ts, tests/codex-host-health-runtime.test.ts, tests/issue-452-empty-503.test.ts → 53 pass / 0 fail; tests/responses-compaction-routing.test.ts + tests/server-auth.test.ts → 138 pass / 0 fail (191 total, 0 fail); bun run typecheck pass; bun run privacy:scan pass
  • Findings:
    1. Confirmed Medium (docs contract) — host-only guarantee overclaimed in all five locales. providers.md line 25 (EN/JA/KO/RU/ZH-CN) says a terminal pre-status rejection "updates only process-local host health ... it does not change account quarantine, account cooldown/failure streak, affinity, pool rotation, or active account selection." Pool-backed sidecar paths (src/web-search/executor.ts:96-98, src/vision/describe.ts:116-118src/providers/openai-sidecar.ts:100-108) still call recordCodexUpstreamOutcome with connect_error/timeout, which does change streak/affinity/rotation. The PR body scopes sidecars out; the docs sentence must name the covered paths (pooled regular Responses + native compact) or the claim stays false. Matches open thread PRRT_kwDOS-0Gi86WU8H4.
    2. Confirmed Low (test quality) — vacuous assertion. tests/codex-host-health-runtime.test.ts:351: runtimeErrorLabel always returns a non-empty string (${name}:${code}:${message} or typeof), and every is true for an empty array, so the assertion cannot fail. Replace with expect(runtimeErrors).toHaveLength(CODEX_UPSTREAM_HOST_FAILURE_THRESHOLD) before the label-shape check. Matches open thread PRRT_kwDOS-0Gi86WVEWF.
    3. Declined with evidence — CodeRabbit "exact runtime classifier" thread. PRRT_kwDOS-0Gi86WVEWE asserts the runtime limits host-neutral handling to a bounded pre-connect classifier and keeps timeouts/aborts/ECONNRESET account-scoped. That describes the removed design: rg isAccountNeutralNetworkError|ACCOUNT_NEUTRAL finds nothing on this head, and transportFailureResponse (src/server/responses/core.ts:1926-1950) / compactTransportFailureResponse attribute every no-status physical rejection to host health. The docs sentence matches the runtime for the covered paths. Reply with this evidence.
    4. Info / confirm intent — attribution shift vs issue text. Issue #914 asked to "preserve current handling for ... TimeoutError, ECONNRESET". On this head, pre-status timeouts and resets (incl. read-then-close) are host-only for pool sends; only observed statuses stay account evidence. This is consistent with the plan's host-ledger direction and is documented in the new docs line, but the PR body should state the shift explicitly.
  • Fixed this session: none (foreign PR; no push)

Security

  • Scope reviewed: outbound/manual redirects, credential replay boundary, probe-lease lifecycle, logging/privacy of attempt observations, AI-proxy defensive surface
  • Decision: Pass; risk Low-Medium residual (availability)
  • Findings: none confirmed exploitable. Manual redirects with no Location exposure are correct; no replay after a credential-visible no-status rejection is correct (alternate sends only run after an observed status); open circuit returns bounded 502 + Retry-After (fail-closed); UpstreamAttemptObservation records no URL/headers/credentials/error text; privacy scan green; no CI/workflow/manifest changes in the diff
  • Residual: a host circuit intentionally blocks later logical requests to the same origin, including healthy alternates, for up to 30 s — documented behavior; read-then-close ECONNRESET is host-attributed, so a credential-gated upstream that closes sockets can open the shared circuit (documented tradeoff, not a bypass)
  • Adversarial/red-team second pass: not requested, skipped
  • Fixed this session: none

Spec / standards

  • Spec source: issue #914, PR body, devlog/_plan/260803_transport_attribution/000_plan.md
  • Gaps: docs overclaim (finding 1); PR body should record the TimeoutError/ECONNRESET attribution change (finding 4); plan's "residual false-negative class documented" criterion is met (docs no-replay + host-circuit sentence)
  • Standards: Bun-native, flat tests near the subsystem, five-locale docs synchronized with each other, typecheck/privacy green; no drive-by scope inside the claimed regular/compact boundary; sidecar removal from the earlier head is a deliberate scope cut, correctly called out in the PR body
  • Advisory smells: duplicated admission/settlement glue between core.ts and compact.ts (see simplify candidates); retryAfterSeconds: 1 literal for half-open contention is fine

Reviews

  • Owners/maintainers: @lidge-jun CHANGES_REQUESTED (review 4841913260) was filed against the old classifier head 888c9558; the host-ledger rewrite addresses the root objection, but the review is still formally in force — after the fixes below, reply on that review and re-request review
  • Owner issue comment 5162870843 (Bun ConnectionRefused breadth): largely superseded by the host-ledger design; worth one closing comment noting refused-port and NXDOMAIN share Bun labels and are intentionally host-scoped
  • Bots: 8 earlier CodeRabbit threads resolved/acknowledged on d6c37343; 3 unresolved on this head — 2 valid (sidecar docs scope PRRT_kwDOS-0Gi86WU8H4; vacuous assertion PRRT_kwDOS-0Gi86WVEWF), 1 stale (classifier PRRT_kwDOS-0Gi86WVEWE, decline with evidence)

Base / CI

  • Behind/conflicts: clean — dev@e44d234f is the merge-base of head d6c37343; GitHub reports MERGEABLE
  • Required checks: enforce-target + label green on this head; Cross-platform CI (run 2877) and React Doctor (run 2024) are action_required (fork approval) and have never executed on d6c37343owner action: request approval/rerun and get the matrix green on this exact head
  • Local tip evidence on d6c37343: 191 tests / 0 fail across all six changed test files (1,846+ assertions), typecheck pass, privacy scan pass, bun install --frozen-lockfile clean
  • Full-suite note: the PR's documented full-suite environment failures (Windows ACL/EBUSY/timeout) reproduce on clean dev per the PR body; not treated as branch regressions

Simplification (for the PR owner)

Foreign PR: candidates only; nothing was edited or pushed.

  1. src/server/responses/core.ts (~2000-2025) and src/server/responses/compact.ts (~520-560) — extract the duplicated "pre-executor failure vs physical attempt already observed" settlement decision into one small shared leaf helper taking explicit callbacks (attemptHistory, host lease, probe release, account-outcome recorder, host-failure recorder). Problem: the two copies already drifted once and will again; the rejection-boundary semantics are identical. Risk: medium — do not extract if it obscures the settlement rules; keep the callers' closures. Validation: tests/responses-compaction-routing.test.ts + tests/server-auth.test.ts.
  2. src/server/responses/core.ts (~1993-2005) and src/server/responses/compact.ts (~500-512) — unify the late "admit after auth" block (if (!lease && hostKey) acquire ... blocked -> 502) into a tiny shared helper. Risk: low. Validation: same suites.
  3. Optional, only after 1: share the transport-failure response shaper (transportFailureResponse / compactTransportFailureResponse). Risk: medium-high because closures differ; a cross-reference comment is an acceptable alternative. No broad dedup of the whole core/compact settlement is recommended in this PR.
  4. The vacuous assertion fix (finding 2) is a review fix, not optional simplification — it also improves the .invalid fixture's failure diagnostics.

Gate

  • ship-gate.mjs (mutation maintainer, workflow references/full-review-pr.md): blocked
  • Blockers: reviewPolicy:changes_requested; reviewThreads:unresolved_review_threads (3); wake — issue comment 5162870843, review submission 4841913260
  • Draft/WIP/do-not-merge: none
  • Base/required-checks components: ready

Bottom line

The redesign is right and the previous round's blockers are genuinely fixed: reject-then-pre-exec evidence is preserved (regular + compact regressions), five-locale failover wording is corrected, temp-home isolation is in place, alternate redirects match the primary nullable-host rule, and thresholds are named constants. Do not merge yet: scope the docs guarantee to the covered paths in all five locales, fix the vacuous assertion, decline the stale classifier thread with runtime evidence, get Cross-platform CI + React Doctor approved and green on d6c37343, and re-request @lidge-jun's review so the stale CHANGES_REQUESTED is re-evaluated. Optional simplify candidates are owner-side only.

@Wibias
Wibias marked this pull request as draft August 4, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants