Skip to content

[WRONG BRANCH] release: promote dev to main for 2.28.0 - #2186

Merged
lidge-jun merged 124 commits into
mainfrom
codex/promote-2.28.0
Aug 20, 2026
Merged

[WRONG BRANCH] release: promote dev to main for 2.28.0#2186
lidge-jun merged 124 commits into
mainfrom
codex/promote-2.28.0

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Summary

Promotes dev (96f288d59) to main for the 2.28.0 release. 122 commits since 8e01dd4e8 (v2.27.0): 46 fix, 36 docs, 4 test, 1 feat, the rest merges.

Why a minor rather than a patch. One user-visible feature landed — the Logs surface now attributes and filters intercepted helper requests (#2179) — and it adds a new column, badge, and filter to a shipped screen. Alongside it, the range carries provider behaviour changes that alter what the proxy sends and accepts, not just what it does on error: xAI OAuth now streams through Responses, Anthropic frames the opening turn, Google replays thought signatures across stream chunks, the Fast capability gate reaches the native passthrough, and opencode-free / OpenCode Go are identified by what they actually do rather than by name. A user upgrading gets new behaviour, not only fewer bugs. That is a minor.

Security-relevant content in this range, all fixed and audited:

The full five-pass audit of this range is in devlog/_plan/260820_bug_pr_backlog_consolidation/100_release_audit.md.

Verification

  • Linux shards, macOS, gates, storage policy, api usage, keyring (3 OS), npm-global (3 OS) — all green at 96f288d59 in run 32344670867.
  • Windows leg dispatched and read at the same head. All four shards completed for the first time in this session — the two previous dispatched runs each lost a shard to the job timeout, so a quarter of the suite had no result at all. Shards 3/4 and 4/4 pass.
  • Four Windows failures remain, all pre-existing, none introduced by main...dev (git log origin/main..origin/dev is empty for every file involved). Three are process-spawn timing on a contended runner; one is an open behaviour question in src/lab/public/signature.ts. All four are recorded on [Bug][Windows]: six pre-existing CI shard failures (WP13 startup cost, symlink fixtures, Bun panic) #2152 with stacks. platform-windows is workflow_dispatch-only and release.yml gates on the push-event run, which is Linux + macOS + gates — that exclusion is deliberate and documented in the workflow.

The release version bump itself is made by scripts/release.ts on main after this merges; this PR carries no version change.

Checklist

  • Tests added or updated (n/a — promotion only, no source change)
  • bun run typecheck passes (green in the gates job at this head)
  • Full suite passes (Linux shards + macOS control at this head)
  • Docs updated if user-facing behavior changed (docs landed with their own PRs in this range)

Summary by CodeRabbit

  • New Features

    • Account-gated model availability now uses confirmed entitlements and fail-closed routing.
    • Routed Responses requests support compatible tool-search handling.
    • Logs can filter intercepted helper requests and show their original model.
    • OAuth flows and provider routing include stronger reliability protections.
  • Bug Fixes

    • Improved streaming tool calls, response IDs, quota handling, and authentication retries.
    • Shell hooks now respond to Claude Code availability.
  • Documentation

    • Updated provider, model, integration, and shell-hook guidance across supported languages.

lidge-jun and others added 30 commits August 20, 2026 02:53
#1686 made a caller that proves admission with one of our own secrets substitute
the stored main credential, so that secret never leaves the process. That is
right for a route that reaches the ChatGPT backend. It was applied by asking HOW
the caller authenticated and never WHERE the request routes, so a request bound
for a key-authenticated provider - which carries its own credential and never
touches ChatGPT - was gated on a credential it has no use for.

An install that deliberately never logged into ChatGPT therefore got 401
"No usable Codex main credential" on every routed request, a regression from
v2.23.0 reported in #2132.

Gate the substitution on route.codexAccountMode, which is set only for the
native openai row and is exactly the test for "this route can consume the stored
ChatGPT credential". Both pool and direct keep substituting, so #1686's Direct
contract is preserved rather than narrowed to pool.

Closes #2132
The ChatGPT backend 400s a gpt-5.6 request that still carries
prompt_cache_retention: "Unsupported parameter". GPT-5.6 replaced the field
with prompt_cache_options.ttl.

Strip it on the canonical ChatGPT forward path for the gpt-5.6 family only.
The retired value is not translated into the replacement field: 5.6 carries a
different TTL contract and implicit caching still applies, so inventing one
would change a caching decision the caller never made.

The narrowness is the fix, not an omission. An older model may still honor the
field, and a self-hosted or third-party forward gateway may still accept it, so
both axes are pinned by non-match tests.

Based on @lilinxiong's implementation in #2102, with an exact-or-dashed-prefix
family match so a future gpt-5.60 is not swept up.

Closes #2092
…s them

Two evidence surfaces read per-model config with a bare map lookup while the
runtime resolves the same keys through modelRecordValue, so family and case
overrides were invisible to them and a prototype-shaped id resolved an
Object.prototype member instead of missing.

Routing capability evidence therefore gave gpt-oss:120b the provider-wide 8k
window instead of the gpt-oss family's 131072, and ignored noVisionModels -
values that select candidates, not just logs. The Lab behavior report missed
the same overrides, and "constructor" resolved to Object.prototype.constructor,
which made jcsStringify throw and silently dropped Lab subjects.

Exact-own maps (modelPreferHostedTools, modelOpenRouterRouting) deliberately do
not family-spread; that boundary is pinned by tests.

Both patches are @ntdatt812's work from #2100 and #2077, applied unchanged.

Closes #2100
Closes #2077
parseUsageQuota filled shortPercent and setAccountQuotaFromParsed dropped it, so
the 5-hour burst window never reached the cache, the accounts DTO, the dashboard,
or routing. A saturated short window was invisible to account selection.

Carries @Ingwannu's #2056: shortPercent joins hasKnownQuotaValue, a new
snapshotHasShort keeps a short-only snapshot from reading as empty, partial
weekly/monthly snapshots no longer clobber a known short window, and
updateAccountQuota carries the tuple.

Also fixes the blocker raised in review on both #2056 and #2062: the scorer took
Math.max over every finite window, so a snapshot carrying only shortPercent: 0
scored a flat 0 and made an account whose long windows were never observed look
like the emptiest in the pool - pickLowestUsageAmong would then send every
request to it. The burst window now refines a known long-window position instead
of standing in for one, and returns CODEX_UNKNOWN_USAGE_SCORE until a governing
window is actually observed.

The ported test asserted the old behavior directly
(computeCodexUsageScore({ shortPercent: 0 }) === 0); it is replaced by a case
that pins the corrected contract in both directions.

Closes #2047
gpt-daybreak-blue-latest is in the static native set, so catalog sync copied it
onto every account selector and Pool could bind a bare Daybreak request to an
account whose authenticated roster never contained it. The upstream answered
"The 'gpt-daybreak-blue-latest' model is not supported when using Codex with a
ChatGPT account."

Make the authenticated ChatGPT roster the source of truth: discover per-account
entitlement, advertise the gated row only where an eligible account confirms it,
and refuse selection of an account that cannot serve it. Discovery failures fail
closed - the row disappears rather than being offered on unproven evidence.

Carries @Ingwannu's #2101, with three corrections:

Selector compact missed the wire rewrite. accountGatedCompactWireModel was
derived from the caller's raw model string, and an account-qualified selector
like side/gpt-daybreak-blue-latest does not match the gated map, so it still
took the native compact endpoint the guard exists to avoid. It now derives from
route.modelId, the same value core.ts normalizes from.

Direct callers shared one 64-entry roster cache with main/Pool. A burst of
distinct Direct callers evicted the very entries the catalog projects from, so
the gated row vanished until rediscovery. The two classes now evict separately.

A comment in native-models.ts still claimed routing never collapses Daybreak
into gpt-5.6-sol, which the wire normalization does exactly.

Stacked on #2137: this consumes the substituteMainCredential value that PR
corrects, so it must not land ahead of it.

Closes #2097
OAuth xai/grok-4.5 and grok-4.6 Codex /v1/responses traffic still used the
provider-wide openai-chat adapter, while the official Grok CLI catalog declares
api_backend: "responses". Chat Completions compatibility holds the stream until
the reasoning turn finishes, so Codex sat blank until the turn was effectively
done.

Declare the Responses wire default for those two models, scoped to OAuth and to
responses-shaped inbound traffic. API-key xAI, Chat/Anthropic translation, other
Grok models, and any explicit modelAdapters override all stay on Chat.

Native Responses returns before the generic recovery loop, so the OAuth 401
replay never ran on this path. Add the equivalent one-shot: refresh once, rebuild
the provider and adapter, replay once. It is a single branch rather than a loop,
so a second 401 cannot refresh again. Refresh failures go through the existing
public OAuth error projector, which the tests pin against path canaries.

Carries @olddonkey's #2104 unchanged.

Closes #1886
…abled

GET /api/subagent-models built `available` purely from currently-pickable
models, so a featured model disabled elsewhere vanished from it. The dashboard
filters `chosen` against `available` and then PUTs exactly the rows it holds,
which turned a hide into a delete: the next Save wrote the truncated roster to
config.json, and the user read it as "ocx service lost my subagent models".

Retain a chosen id in `available` when it is not otherwise selectable, appended
after the selectable set and deduplicated. Models that are disabled and NOT in
the roster stay excluded, so the picker behavior is unchanged for every model
the user has not deliberately featured.

The combo test asserted the old truncating behavior; it now asserts retention
while a roster slot is held, and full exclusion once the slot is released.

Closes #2133
…claims

opencode-free sent no User-Agent, so Zen saw the bare runtime default
(Bun/x.y.z) and rate-limited it harder than a client that identifies
itself. Adds "User-Agent: opencode" alongside the existing
x-opencode-client: desktop marker.

The value is deliberately unversioned. OmniRoute, an independent
open-source broker against the same Zen upstream, defaults to exactly
this pair and reached it by retreating from its own earlier
opencode-cli/1.0.0 pin: a pinned version is a claim about an install we
do not have, and it goes stale on the vendor's schedule.

The registry edit alone would have shipped to nobody. staticHeaders is
documented as merged into every upstream request, but it was only ever
copied at seed time, so any config written before a header existed --
or carrying any header of its own -- never received it.
routedProviderConfig and buildModelsRequest now fill registry static
headers beneath user headers, matched case-insensitively so an override
replaces rather than duplicates: spreading "User-Agent" over a user's
"user-agent" leaves both keys, which Headers serializes as one
comma-joined value.

Model discovery gets the same treatment because a provider identified
as opencode when it completes but anonymous when it lists its own
models reads as two different clients to a rate limiter.
…non-English

AgentRouter answers 400 content-blocked when the first user message is
not in English (#2074) while the identical English request returns 200.
The gateway inspects the opening user content, so an Anthropic system
string never reaches the filter -- the framing has to sit in that turn.

Two corrections on top of @yzxcj797's #2082.

The host test was hostname.includes("agentrouter"), which also matches
notagentrouter.example and agentrouter.org.attacker.example. A prompt
mutation keyed on a provider's identity has to be keyed on that identity
exactly, so this matches agentrouter.org or a real subdomain of it.

The original spliced the marker into the user's own string. That edits
what the user wrote: logs, retries, and any upstream echo then show a
sentence the user never typed as if they had. The framing is now its own
leading text block, so the original text survives byte-for-byte.

Idempotence is keyed on the leading block being exactly the marker
rather than a substring test, so a user who quotes the marker later in
their prompt does not suppress their own framing.
…amed

A multi-account setup points several provider rows at the same OpenCode
Go endpoint under names the registry has never heard of --
opencode-go-2 through -5. Quota dispatch gated on the literal name
"opencode-go", so those rows had no dashboard quota panel and no report
in `ocx provider quota --refresh --json` even though each one holds a
working key for the same upstream (#1924).

Identity is now answered by registryEntryForProviderDestination, the
predicate this repository already uses for renamed fixed-key rows: it
matches on normalized endpoint plus adapter plus key auth. A bare URL
comparison would have been enough for the reported symptom but would
also probe a row that points at that host through a different adapter,
which speaks a different protocol and is not the provider whose quota
shape we parse.

The defensive canonical-URL check inside fetchOpenCodeGoQuota stays.
Whether an API key may be sent to a host must not depend on the dispatch
gate above it being correct.

Absorbed from #2027 by @yzxcj797.
…d is known

Some OpenAI-compatible streamers repeat an already-sent id, name, or
arguments as a non-string placeholder on a continuation delta rather than
as null. Validation ran before the pending-call lookup, so the whole turn
died with a 502 and the tool never ran -- even though the value being
repeated was already held in canonical form.

The lookup now happens first and tolerance is per field, keyed on that
field's own provenance. Two corrections on top of @waw4303's #2155.

It gated arguments acceptance on the call having a canonical NAME. A name
says nothing about whether arguments was ever sent as a string, so a real
argument payload could be silently dropped. PendingToolCall now carries
sawArgumentsString; an empty string counts, because it proves the upstream
sent the field with the right wire type.

It also left a non-string repeated id unconditionally terminal even after
a canonical id was stored. Ids now follow the same rule as the other two.

Diagnostics are passed from the rejection site instead of rescanned.
A stateless rescan stops at the first structurally odd value, so a stream
carrying accepted padding on call 0 and a real defect on call 1 blamed
call 0.
… to a constant

`tests/ws-upstream.test.ts` has two cases failing on Windows since 5a75e57:

    (fail) an HTTP fallback remains on the configured legacy tee path
    (fail) an older runtime stays on HTTP SSE without opening a WebSocket

Measured, not inferred -- both bisect endpoints were run rather than assumed:

    dec332c   23 pass / 0 fail
    5a75e57   21 pass / 2 fail    fix(grok): ... backfill required annotations

That commit adds `createResponsesFieldBackfillBlockRewrite()` to `blockRewrites`
unconditionally, and the factory returns an `SseBlockRewrite` rather than
`undefined`, so the chain is never empty and `needsClientRewrite` in
`handleResponses` is now a constant `true`. `isWin32EagerRewrite` is
`platform === "win32" && needsClientRewrite` (src/lib/bun-stream-caps.ts:126),
so on Windows every Responses stream now takes the eager single-reader relay --
which is exactly what #864 asks for, since all traffic is now rewrite traffic.

Instrumented at the gate to confirm the mechanism rather than deduce it:

    [EAGER] {"forceCodexWsEagerRelay":false,"useEagerRelay":null,
             "win32EagerRewrite":true,"needsClientRewrite":true,
             "platform":"win32","blockRewrites":1}

So the source behaviour is intended and the assertions are stale. Both cases are
about the *WebSocket* path not being taken, and both already assert that
directly through `FakeWebSocket.instances`; the `isEagerRelaySseResponse(...)`
assertion was a second-order signal that stopped tracking WS selection on win32.

Holds it to the documented rule instead of to `false`, so it stays honest on
every platform rather than encoding a pre-backfill world.

Adds one precondition case pinning the coupling itself -- the rewrite chain
being non-empty, and the platform rule -- so if either half moves it fails
somewhere that names the real cause instead of inside a WebSocket assertion.

Tests only; no src change. 24 pass / 0 fail in the file (was 21/2), and the new
case is mutation-checked: forcing `isWin32EagerRewrite` to `false` turns it red.
58 pass / 3 skip / 0 fail across ws-upstream, responses-field-backfill,
responses-snapshot-repair-server and subagent-fallback-handle-responses.
`bun run typecheck` exit 0.
…ot the factory

The `eager-relay marker preconditions` test asserted only that
`createResponsesFieldBackfillBlockRewrite()` returns a function. That would stay
green if `handleResponses` stopped adding it to `blockRewrites`, so it did not
actually protect the contract the two marker assertions depend on.

Replace it with an integration case in the existing `handleResponses` describe:
drive a Responses stream whose `output_text` part omits the required
`annotations` field, then read the client bytes back. Seeing `annotations: []`
there is only possible if the rewrite is registered and ran, which is exactly
what makes `clientBlockRewrite !== undefined` and `needsClientRewrite === true`.

The platform half stays a pure unit test on the real exported helper.

Verified on win32, exact head:
- bun run typecheck                exit 0
- bun test tests/ws-upstream.test.ts   25 pass / 0 fail

Mutation-checked:
- dropping `createResponsesFieldBackfillBlockRewrite()` from `blockRewrites`
  fails the new case on `toHaveProperty("annotations")` (3 fail)
- widening `isWin32EagerRewrite` past win32 fails the truth table (1 fail)

Still test-only; no runtime change.
test(ws-upstream): hold the eager-relay marker to the win32 rule, not to a constant
Some relays omit the required id on message, reasoning, and function_call output
items, so strict decoders reject the response even after #1941. Synthesize a
stable msg_ocx_N / rs_ocx_N / fc_ocx_N id keyed on output_index, and never
overwrite an id the upstream actually sent.

Carries @bet4it's #2131 implementation and tests.

One correction on top: an absent or malformed output_index collapsed to 0, so
two such items both became msg_ocx_0 - duplicate ids, which is the defect this
backfill exists to prevent. An unusable index now falls back to a monotonic
ordinal based far above any plausible real index, so a synthesized id cannot
collide with an index-derived one. The well-formed path is unchanged and still
produces the stable index-derived id.

Locale docs are limited to the English source here; the translated guides in the
original PR were uneven and locale parity is not this change's thesis.

Closes #2131
A heartbeat is adapter liveness, not turn content. guardTerminalEventStream
pushed every nonterminal event into `seen`, and `seen` feeds both the
continuation analysis and the rebuilt request. The openai-chat adapter now
emits one heartbeat per tool-call delta, so a single large argument payload
could grow that array without bound on a provider with
terminalContinuationGuard enabled.

The empty-completion guard already passes heartbeats through unretained;
this matches it. They still reach the consumer, because the bridge needs
them to re-arm its stall watchdog.

Also corrects the attribution on the heartbeat itself. It was described as
fixing #2156, and it does not: the reporter's error is emitted after the
adapter reads EOF with pending tool calls, while a stall timeout produces
response.incomplete with reason upstream_stall_timeout on a path the bridge
has already closed. The heartbeat fixes a real false-stall hazard; #2156
needs the reporter's raw SSE comparison before anyone can say what closed
that stream.
The badge is "I · <model>" -- a one-glyph marker plus a model id, sitting
inside a narrow table column. The glyph is an icon-shaped affordance, not
a word, and its meaning is carried by the tooltip
(logs.badge.interceptedHelperTitle), which every locale does translate.

Localizing the glyph per locale would make the same badge unrecognizable
across a screenshot or a bug report while adding nothing to
comprehension, so it joins models.shadowCallOriginal on both
intentional-English allowlists rather than being translated.

The parity tests were right to flag it; English is the intended
rendering, which is exactly what those allowlists exist to record.
…r had

Six shard failures in three groups (#2152). None came from main..dev;
all three needed a different answer, and none of them was skipping a
test that can actually run.

Group 1, budgets. watchdogMs is a FLOOR, not a multiplier, so a case
calling watchdogMs(30_000) still got exactly 30s -- 'Restore truth'
failed at 30,147ms. Windows CI now floors at 45s, under the lane's own
60s per-test timeout so a hung test stays bounded.

'A-reduced' was misread in the issue: its 79,978ms was elapsed time
against a 150s ceiling, so the outer budget was never the constraint.
The real failure was Fixture.request's unscaled 10s AbortSignal, which
aborted the case from inside. It is scaled now like every neighbouring
budget.

'E' does not start ocx at all. Its lock holder released after a fixed
3s busy wait, and on a Windows shard the contender's process spawn can
outlast that -- the parent then sees 'acquired' where it demands 'busy',
which reads as a broken exclusion invariant rather than a hold that
expired early. The release-marker handshake still ends the hold early
everywhere else; only the ceiling moved.

Group 2, skip guard. The issue says an unprivileged Windows user cannot
create symlinks, but the GitHub runner can -- so canSymlink was true,
the cases ran, and they failed on how the preflight reads mode and
access through a Windows symlink. Two neighbouring cases in the same
file already skip on process.platform === "win32"; these three now use
that same guard, and keep the capability check for unprivileged POSIX.

Group 3, crash retry. A Bun panic is a crash in the interpreter, not a
test result. The macOS leg has carried a crash-signature retry for this;
the Windows shards, a separate matrix job with their own one-shot
command, had none. They now use the same wrapper, extended with
panic(thread since that is the signature this leg actually printed.
An assertion failure returns its status immediately and is never
retried.

What this cannot prove locally: whether 45s is sufficient under real
Windows shard contention, the actual skip result on the runner, and
PIPESTATUS behavior in Git Bash. Those need a Windows CI dispatch, which
is the evidence to look for on this PR.
…able

The Windows retry added for #2152 grepped for `panic(thread`. This
repository already learned that is the wrong anchor: Bun emits BOTH
`panic(thread 2852)` and `panic(main thread)` for the same class of
failure, and devlog/_fin/260731_pr_issue_triage_round/050_windows_ci_flake_rca.md
names `Internal assertion failure` as the stable fingerprint. Verified by
literal probe -- panic(thread 3960) matched, panic(main thread) did not.
The shard would have failed on exactly the crash the retry exists for.

All three signature lists -- the macOS inline grep, the new Windows one,
and is_bun_runtime_crash in run-bun-test-batches.sh -- now carry the same
alternatives. The workflow comment already required them to stay in sync;
nothing enforced it, so three copies drifted into two shapes.

The contract test now pins the sync itself rather than the text, and pins
that no list keys on the thread-numbered form. hasShellCommandHead is
added because the existing exact-line matcher rejected the `| tee` the
retry requires, while still rejecting an echoed or commented-out copy.
fix(ci): give the Windows leg the budgets and the crash retry it never had
…-attribution

feat(gui): show and filter intercepted helper requests in Logs
…eartbeat

fix(openai-chat): heartbeat while buffering tool-call deltas
…n shards

Run 32340498394 dispatched the Windows leg at the release head and produced
three results that were not defects in this repository:

1. shard 1/4 was CANCELLED at 15m12s while still executing tests. That is
   neither a pass nor a fail, and it silently removed the composed-acceptance
   cases from the evidence. The other shards finished at 14-15 minutes, so 15
   was inside the noise band rather than above it. Raised to 25, which still
   kills a wedged shard and now also covers the second attempt the crash retry
   is allowed to make.

2. `Responses previous_response_id state > orphan cleanup obeys scan and
   cleanup caps` ran 100.6s against a 90s budget on shard 4/4 while doing
   exactly the work it claims: 521 individually fsync'd durable writes. The
   number was sized from a ~34s windows-latest measurement and was measuring
   runner contention, not a hang. BULK_DURABLE_IO_BUDGET_MS now carries a
   Windows-only 180s ceiling, the same shape as the watchdogMs floor.

3. `Claude Code shell-hook reconciliation > does not treat a non-executable
   claude file as an installed CLI` writes mode 0o644 and expects
   claudeCodeCliInstalled() to be false. Windows has no execute-permission
   bit, so accessSync(path, X_OK) succeeds for any readable file and the
   fixture cannot express its own precondition. It now skips on win32, as
   several neighbouring symlink cases already do.

Refs #2152.
…n-and-budgets

fix(ci): stop the Windows leg from truncating and mismeasuring its own shards
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 20, 2026 07:57
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot changed the title release: promote dev to main for 2.28.0 [WRONG BRANCH] release: promote dev to main for 2.28.0 Aug 20, 2026
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • wrong target branch (main); retarget to dev. UI screenshot required.

What to do

  • Retarget this PR to dev — all contributions go to dev.
  • Add a screenshot of the UI change to the PR description.

Its title has been prefixed with [WRONG BRANCH].
Automatic draft conversion failed (token cannot change draft status). Please convert this pull request to a draft manually. The required enforce-target check will keep failing until every issue above is resolved.

@github-actions
github-actions Bot marked this pull request as draft August 20, 2026 07:58
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6139b439-db61-4e5c-aaa3-52c2bb650bb5

📥 Commits

Reviewing files that changed from the base of the PR and between 8e01dd4 and 96f288d.

⛔ Files ignored due to path filters (2)
  • devlog/_plan/260820_bug_pr_backlog_consolidation/assets/2157-logs-intercepted-badge.png is excluded by !**/*.png
  • devlog/_plan/260820_bug_pr_backlog_consolidation/assets/2157-logs-intercepted-filtered.png is excluded by !**/*.png
📒 Files selected for processing (138)
  • .github/workflows/ci.yml
  • devlog/_plan/260820_bug_pr_backlog_consolidation/000_research_inventory.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/010_layer1_bearer_admission_2132.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/020_layer2_responses_id_backfill_2131.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/030_sibling_prompt_cache_retention.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/040_sibling_routing_capability.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/050_sibling_k12_short_window.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/060_supersede_and_close_operations.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/070_execution_log.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/080_residual_dispositions.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/100_release_audit.md
  • devlog/_plan/260820_bug_pr_backlog_consolidation/100_release_safety_audit.md
  • docs-site/src/content/docs/fr/guides/claude-code.md
  • docs-site/src/content/docs/guides/claude-code.md
  • docs-site/src/content/docs/guides/codex-app-models.md
  • docs-site/src/content/docs/guides/grok-build.md
  • docs-site/src/content/docs/ja/guides/claude-code.md
  • docs-site/src/content/docs/ja/reference/adapters.md
  • docs-site/src/content/docs/ko/guides/claude-code.md
  • docs-site/src/content/docs/ko/reference/adapters.md
  • docs-site/src/content/docs/reference/adapters.md
  • docs-site/src/content/docs/ru/guides/claude-code.md
  • docs-site/src/content/docs/tr/guides/claude-code.md
  • docs-site/src/content/docs/zh-cn/guides/claude-code.md
  • docs-site/src/content/docs/zh-cn/reference/adapters.md
  • docs-site/src/content/docs/zh-tw/guides/claude-code.md
  • gui/src/i18n/de.ts
  • gui/src/i18n/en.ts
  • gui/src/i18n/fr.ts
  • gui/src/i18n/ja.ts
  • gui/src/i18n/ko.ts
  • gui/src/i18n/ru.ts
  • gui/src/i18n/tr.ts
  • gui/src/i18n/zh-TW.ts
  • gui/src/i18n/zh.ts
  • gui/src/pages/Logs.tsx
  • gui/tests/fr-localization.test.ts
  • gui/tests/locale-parity.test.ts
  • gui/tests/logs-auto-refresh.test.tsx
  • scripts/ci/run-bun-test-batches.sh
  • scripts/release.ts
  • src/adapters/anthropic.ts
  • src/adapters/base.ts
  • src/adapters/google-antigravity-replay.ts
  • src/adapters/google.ts
  • src/adapters/openai-chat.ts
  • src/adapters/openai-responses.ts
  • src/cli/index.ts
  • src/codex/account-usability.ts
  • src/codex/auth-api.ts
  • src/codex/auth-context.ts
  • src/codex/catalog/metadata.ts
  • src/codex/catalog/native-models.ts
  • src/codex/catalog/sync.ts
  • src/codex/convergence.ts
  • src/codex/model-entitlements.ts
  • src/codex/quota.ts
  • src/codex/routing.ts
  • src/lib/destination-policy.ts
  • src/lib/shadow-call.ts
  • src/oauth/index.ts
  • src/oauth/store.ts
  • src/providers/fastwire.ts
  • src/providers/quota.ts
  • src/providers/registry.ts
  • src/providers/service-tier.ts
  • src/responses/parser.ts
  • src/responses/tool-search-compat.ts
  • src/router.ts
  • src/routing/capability.ts
  • src/routing/compatibility/behavior.ts
  • src/server/chat-native.ts
  • src/server/index.ts
  • src/server/management/agent-settings-routes.ts
  • src/server/request-log.ts
  • src/server/responses-tool-search-repair.ts
  • src/server/responses/compact.ts
  • src/server/responses/core.ts
  • src/server/responses/responses-field-backfill.ts
  • src/server/responses/terminal-guard.ts
  • src/server/system-env.ts
  • src/usage/log.ts
  • structure/03_catalog-and-subagents.md
  • structure/04_transports-and-sidecars.md
  • structure/05_gui-and-management-api.md
  • structure/08_openai-provider-tiers.md
  • tests/adapter-resolve.test.ts
  • tests/anthropic-agentrouter-language-framing.test.ts
  • tests/anthropic-baseurl-override.test.ts
  • tests/antigravity-baseurl-override.test.ts
  • tests/bearer-admission-routed-provider.test.ts
  • tests/ci-workflows.test.ts
  • tests/claude-models-discovery.test.ts
  • tests/claude-shell-hook.test.ts
  • tests/codex-auth-api.test.ts
  • tests/codex-auth-context.test.ts
  • tests/codex-catalog-sync-hardening.test.ts
  • tests/codex-composed-acceptance.test.ts
  • tests/codex-convergence-account-selectors.test.ts
  • tests/codex-model-entitlements.test.ts
  • tests/codex-routing.test.ts
  • tests/combo-management-api.test.ts
  • tests/core-lab-boundary.test.ts
  • tests/fastwire-characterization-wire.test.ts
  • tests/fastwire-policy.test.ts
  • tests/google-antigravity-replay.test.ts
  • tests/google-signature-history-roundtrip.test.ts
  • tests/helpers/ci-watchdog.ts
  • tests/helpers/codex-write-lock-child.ts
  • tests/helpers/test-budget.ts
  • tests/management-provider-validation.test.ts
  • tests/native-model-toggle.test.ts
  • tests/oauth-public-surface.test.ts
  • tests/openai-chat-eof.test.ts
  • tests/openai-chat-hardening.test.ts
  • tests/openai-chat-native-policy.test.ts
  • tests/openai-responses-passthrough.test.ts
  • tests/opencode-free-provider.test.ts
  • tests/opencode-go-quota.test.ts
  • tests/openrouter-provider-routing.test.ts
  • tests/provider-model-discovery-contract.test.ts
  • tests/provider-registry-parity.test.ts
  • tests/request-log.test.ts
  • tests/responses-field-backfill.test.ts
  • tests/responses-parser.test.ts
  • tests/responses-shadow-intercept.test.ts
  • tests/responses-tool-search-repair.test.ts
  • tests/router-discarded-baseurl-warning.test.ts
  • tests/router-template-baseurl.test.ts
  • tests/routing-capability-model-matching.test.ts
  • tests/routing-compatibility-model-matching.test.ts
  • tests/server-auth.test.ts
  • tests/server-xai-oauth-401-replay.test.ts
  • tests/server-xai-responses-streaming.test.ts
  • tests/subagent-roster-retention.test.ts
  • tests/terminal-guard.test.ts
  • tests/update-npm-cache-preflight.test.ts
  • tests/ws-upstream.test.ts

📝 Walkthrough

Walkthrough

The pull request combines CI hardening, provider and Responses routing updates, Codex entitlement gating, OAuth persistence protection, shell-hook reconciliation, request-log metadata, GUI filtering, release idempotency, documentation updates, and regression coverage.

Changes

Runtime and provider behavior

Layer / File(s) Summary
Codex entitlement and catalog gating
src/codex/*, src/server/index.ts, src/server/responses/*, tests/codex-*, tests/server-auth.test.ts
Account-gated native models now use authenticated per-account /models rosters. Catalog rows and account selection fail closed when entitlement is absent. Daybreak requests use the canonical wire model and bounded retries.
Responses and adapter compatibility
src/responses/*, src/server/responses*.ts, src/adapters/openai-responses.ts, src/adapters/google*.ts, tests/responses-*, tests/google-*
Routed tool_search requests are lowered to function tools and restored in JSON and SSE responses. Missing output IDs are backfilled. Google thought signatures persist across streamed chunks.
Provider routing and service tiers
src/providers/*, src/router.ts, src/adapters/openai-chat.ts, src/server/chat-native.ts, src/routing/*, tests/fastwire-*, tests/routing-*
Registry defaults can depend on authentication mode and can suppress caller service tiers. Chat tier decisions use one policy authority. Model capability and compatibility lookups use family-aware or exact-own-property rules.
Authentication, transport, and shell integration
src/codex/auth-*.ts, src/oauth/*, src/lib/destination-policy.ts, src/server/system-env.ts, src/cli/index.ts, tests/oauth-*, tests/claude-*, tests/*baseurl*
OAuth persistence checks login ownership before writes. OAuth base-URL overrides require secure transport except for allowed local relays. Claude shell hooks reconcile with CLI availability and system-environment state.
Logging and management surfaces
src/server/request-log.ts, src/usage/log.ts, src/lib/shadow-call.ts, gui/src/pages/Logs.tsx, gui/src/i18n/*, src/server/management/*, tests/request-log.test.ts, gui/tests/*
Shadow-call origin metadata is sanitized and persisted. The Logs page validates, displays, and filters intercepted helper entries. Saved subagent models remain visible when unavailable or disabled.
Quota, CI, release, and documentation support
src/codex/quota.ts, src/codex/routing.ts, .github/workflows/ci.yml, scripts/ci/*, scripts/release.ts, docs-site/*, structure/*, devlog/_plan/*, tests/helpers/*, tests/ci-workflows.test.ts
Short-window quota data is preserved and scored only with a governing long window. Bun crash retries, Windows timeouts, release reruns, translated documentation, and planning or audit records are updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/promote-2.28.0

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.6)
tests/codex-model-entitlements.test.ts

File contains syntax errors that prevent linting: Line 9: Declarations inside of a import declaration may not have duplicates; Line 11: Declarations inside of a import declaration may not have duplicates


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.

❤️ Share

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

if (!antigravityUsesReplayCache(model) || !Array.isArray(parts) || parts.length === 0) return;
export function observeAntigravityReplay(
model: string,
sessionId: string,

@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: 96f288d595

ℹ️ 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 src/server/index.ts
Comment on lines +927 to +934
const bareEligibleAccountIds = providerCodexAccountMode(
OPENAI_CODEX_PROVIDER_ID,
config.providers[OPENAI_CODEX_PROVIDER_ID],
) === "direct" ? new Set([MAIN_CODEX_ACCOUNT_ID]) : undefined;
const availableBareGatedNativeSlugs = availableAccountGatedNativeModels(
modelEntitlements,
bareEligibleAccountIds,
);

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 Include the Direct caller in model discovery entitlements

When the canonical OpenAI provider is in Direct mode and a remote client authenticates with x-opencodex-api-key while forwarding its own ChatGPT Authorization header, request admission correctly checks that caller's roster, but /v1/models restricts gated bare models to __main__. If the local main account is absent or lacks Daybreak while the caller is entitled, discovery hides gpt-daybreak-blue-latest, so Codex cannot select a model that the subsequent request path would accept. Resolve the caller's entitlement for this Direct, non-substitution case, or otherwise keep discovery consistent with resolveCodexAuthContext.

Useful? React with 👍 / 👎.

Comment on lines +183 to +185
const item = typeof rawIndex === "number" && Number.isInteger(rawIndex) && rawIndex >= 0
? backfillOutputItem(event.item, { kind: "index", index: rawIndex })
: backfillOutputItem(event.item, nextSyntheticItemSlot());

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 Preserve synthesized IDs across an item lifecycle

When an upstream omits both item.id and a valid output_index, every output_item.added and output_item.done event calls nextSyntheticItemSlot() independently. The two events for the same logical item therefore receive different IDs, and the terminal snapshot receives yet another index-derived ID, breaking clients that correlate lifecycle events by item ID. Keep a per-stream mapping for malformed-index items so all representations of one item reuse the same synthesized ID rather than allocating a fresh process-global ordinal for each frame.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants