Skip to content

[WRONG BRANCH] fix(responses): prevent cross-request reasoning replay - #37

Closed
luvs01 wants to merge 134 commits into
mainfrom
codex/fix-global-reasoning-replay-cache-leak
Closed

[WRONG BRANCH] fix(responses): prevent cross-request reasoning replay#37
luvs01 wants to merge 134 commits into
mainfrom
codex/fix-global-reasoning-replay-cache-leak

Conversation

@luvs01

@luvs01 luvs01 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Motivation

  • The in-process reasoning replay cache could leak hidden assistant reasoning across unrelated requests when a tool call ID collided or was unscoped, risking disclosure and context poisoning.
  • Call IDs are provider/client-controlled and not globally unique, so the cache must not fall back to a process-wide namespace.

Description

  • Require an explicit conversation scope for cache operations and stop using a global fallback by making rememberReasoningForCall and peekReasoningForCall fail closed when scope is absent, and key entries with scope + '\u0000' + callId (changes in src/responses/reasoning-replay-cache.ts).
  • Remove the implicit "global" replayCacheScope fallback and propagate only the explicit client thread id (parsed._clientThreadId) through the bridge, adapters, image loop, and web-search loop (changes in src/bridge.ts, src/adapters/openai-chat.ts, src/images/loop.ts, src/web-search/loop.ts, and src/server/responses/core.ts).
  • Update tests to exercise scoped behaviour and assert that unscoped cache reads/writes are rejected, and adapt existing replay tests to set a test REPLAY_SCOPE (changes in tests/deepseek-reasoning-replay-gaps.test.ts and tests/bridge-raw-reasoning-hidden.test.ts).

Testing

  • Ran the focused regressions: bun test tests/deepseek-reasoning-replay-gaps.test.ts tests/bridge-raw-reasoning-hidden.test.ts and the modified tests passed (23/23 in those runs).
  • Ran full project checks: bun run typecheck, bun run test, and bun run privacy:scan, all of which completed successfully.
  • Verified repository hygiene with git diff --check and a clean working tree after the changes.

Summary by CodeRabbit

  • Bug Fixes

    • Improved reasoning replay-cache isolation by requiring an explicit conversation scope.
    • Prevented requests without a conversation identifier from sharing or accessing globally scoped cached reasoning.
    • Updated streaming, batch, image, and web-search flows to preserve conversation-specific cache boundaries.
  • Tests

    • Expanded coverage for scoped replay caching, including isolation and unscoped-access scenarios.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 10995506-3915-48a0-b0b9-64edec0a5f27

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change removes the "global" reasoning replay-cache fallback. Cache reads and writes now require an explicit client thread scope. Response paths and tests pass explicit scopes and verify isolation.

Changes

Reasoning replay-cache scoping

Layer / File(s) Summary
Require explicit cache scopes
src/responses/reasoning-replay-cache.ts
keyFor, rememberReasoningForCall, and peekReasoningForCall no longer use or accept missing scopes.
Propagate client thread scopes
src/adapters/openai-chat.ts, src/server/responses/core.ts, src/bridge.ts, src/images/loop.ts, src/web-search/loop.ts
Response and tool paths pass _clientThreadId directly. Requests without a client thread ID leave replayCacheScope undefined.
Validate scoped cache behavior
tests/bridge-raw-reasoning-hidden.test.ts, tests/deepseek-reasoning-replay-gaps.test.ts
Tests use explicit test scopes and verify that unscoped cache entries are rejected and cannot be retrieved.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: wibias, lidge-jun

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 describes the main change: preventing cross-request reasoning replay; the branch marker adds minor noise but does not make the title misleading.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-global-reasoning-replay-cache-leak

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

@github-actions github-actions Bot changed the title fix(responses): prevent cross-request reasoning replay [WRONG BRANCH] fix(responses): prevent cross-request reasoning replay Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

⏳ 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].
This pull request was already a draft. Its draft status will be preserved after every issue above is resolved.

@github-actions
github-actions Bot marked this pull request as draft August 7, 2026 07:31
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deterministic PR hygiene checks passed.

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

luvs01 commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Wibias and others added 28 commits August 11, 2026 04:08
…ctiveness-producer

feat(lab): CL-07 task effectiveness evidence
Back up Compatibility Lab CL-07 closure on dev at merge 02e62fc with accepted head 0efe2c6.
Record CL-06 closure in stack status, fix master plan row, and tag merge/accepted heads to match CL-07 backup practice.
The resolve-pr job mapped a trusted CodeRabbit status SHA back to a PR via
the commit-to-PR association index. That index can lag a fresh head push
(seen on PR lidge-jun#1441), returning zero matches for a genuine current head, so
revalidation was silently skipped and new unresolved findings never
re-drafted the PR.

When the index yields no unique match, fall back to reconciling directly
against the live open-PR list by head.sha — the same authoritative identity
the write gate uses. Resolution still requires exactly one open match, so
ambiguity remains fail-closed. The fallback is a read and stays within the
resolver's pull-requests:read permission.

Add behavioural harness tests for the resolver: empty-index fallback,
no-match skip, fresh-index direct resolve, and fail-closed on error.
Address CodeRabbit feedback on the resolver tests:

- Add a regression test for the ambiguous case: two open PRs sharing the
  same head SHA must fail closed (no pull-number output), instead of only
  covering zero and one matches.
- Correct the fail-closed test so both resolution calls reject. It
  previously only failed pulls.list, so the association-index error path
  was never exercised; assert both warning messages now.
Address CodeRabbit feedback on the ambiguous fallback test: assert that the
resolver actually consults the live open-PR list (one pulls.list call with
the open-state filter) so the test cannot pass without the head-SHA
reconciliation path. The empty-output and ambiguous/stale log assertions
are unchanged.
The ambiguous-head regression (two open PRs sharing the same head.sha) only
asserted empty output and the skip log. Without a pulls.list assertion it
would pass even if the head-SHA fallback were removed, because the empty
association index by itself already causes the skip.

Assert the live fallback ran: exactly one pulls.list call with the
open-state filter, alongside the retained empty-output and skip-log
assertions. The neighbouring zero-match test keeps its own independent
fallback assertion.
fix(ci): reconcile PR from head SHA when the commit-PR index lags
* fix: recognize legacy Team capacity

Treat upstream plan 'team' as the Business-tier configured weight so valid Codex pools are not excluded from dashboard capacity estimates.

* test: add all-Team pool capacity regression

---------

Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
…ice grant + free/paid live catalog (lidge-jun#1397)

* feat(providers): add Nous Portal (Nous Research) OAuth provider — device grant + free/paid live catalog (Closes lidge-jun#1148)

* feat(providers): seed Nous Portal free models from live Portal list (hy3, laguna-s/xs, step-3.7-flash)

* test(nous-oauth): cover device-flow error paths and refresh-token fallback

- access_denied / expired_token surface as terminal NousTokenError
- slow_down backs off (interval bump) then resumes polling to success
- authorization_pending until deadline raises a timed-out error
- refresh omitting a new refresh_token keeps the previous one (header sent)

* fix(oauth/nous): enforce HTTPS base URL and single-use refresh rotation; docs + tests

Addresses the two CHANGES_REQUESTED blockers on PR lidge-jun#1397:

1. resolvePortalBaseUrl() now hard-validates the full OAuth base URL via
   new URL() and throws BEFORE any fetch is dispatched: rejects non-HTTPS
   schemes, embedded credentials, query strings, and fragments; returns
   only url.origin. Aligns opencodex with Hermes hermes_cli/auth.py
   (_NOUS_PORTAL_ALLOWED_HOSTS, https-only) and prevents the single-use
   refresh token / inference JWT from ever traversing cleartext.

2. parseTokenPayload() no longer falls back to the submitted refresh token.
   A response that omits refresh_token, or returns a replacement equal to
   the submitted token, throws NousTokenError(oauthError:
   'refresh_token_reused') so the next refresh cannot replay a consumed
   credential and trigger session revocation.

Also:
- tests/nous-oauth.test.ts: HTTPS/URL hardening (fetch never reached),
  missing/equal refresh rejection, and NousTokenError.oauthError contract
  on access_denied / expired_token.
- tests/nous-oauth-live.test.ts: opt-in, CI-skipped live verification that
  reads the local refresh token without printing it (lengths only), asserts
  rotation + read-only /v1/models reachability. No provider key is shared.
- docs ru/guides/providers.md: eight OAuth presets, ocx login nous, nous row.

Verified: tsc --noEmit, bun test nous-oauth (17/17), privacy:scan passed,
targeted suite 186/186. Full bun run test in progress.

* fix(oauth/nous): failure-atomic refresh, terminal errors, scope check, redirect guard

Addresses the 10 review points from Wibias on PR lidge-jun#1397:

- #2 Single-use refresh is now failure-atomic. A durable refresh-intent file
  (keyed by a sha256 of the refresh token, never the token in cleartext) is
  written before the refresh request and cleared only after the rotated token
  is obtained. If the server responds but the rotation cannot be persisted, the
  intent is marked 'uncertain' and a later refresh REFUSES to replay the
  possibly-consumed token (NousTokenError refresh_token_reused, terminal) —
  forcing a clean re-auth instead of a session-revoking replay.
- #3 Credential-bearing OAuth requests (device + token) now pass
  redirect: 'error' so custom auth headers cannot follow a cross-origin
  redirect.
- #4 invalid_token (and invalid_grant/revoked/revoked_token) are now terminal
  NousTokenError values that drive re-authentication.
- #5 The returned access-token JWT scope is validated for inference:invoke
  before the credential is treated as usable. An insufficient-scope token is a
  terminal error that STILL surfaces the already-rotated refresh token, so the
  caller can persist it and re-auth without discarding the rotation.
- #6 Live /models test accepts both the OpenAI-style { data: [...] } body and a
  bare array (production contract).
- #7 freeTier is no longer true for the mixed free/paid provider; free models
  are classified at model level (the :free slugs). Parity test updated.
- #8 pollForToken parses the response body once and passes the payload through
  to the error path instead of re-reading a consumed body.
- #9 sleep() now removes its abort listener on both resolve and abort, so
  polling iterations do not accumulate listeners.
- #1 The live test is now non-destructive: it persists the rotated token back
  through mergeAccountCredential (prod path), so the local session stays valid.
- #10 Russian docs already mirror the English source (8 presets, ocx login
  nous, nous table row with device grant + single-use rotation).

No provider API key is shared; privacy:scan passes. Verified: tsc --noEmit,
nous-oauth 21/21, provider-registry-parity + targeted suite 193/193.

* fix(oauth/nous): close the uncertain-outcome window for single-use refresh

Deep re-review (real execution proof) showed the first intent design still
relied on the server to refuse a replay when the rotated token was obtained
but lost before the store persisted it. Harden the contract:

- The refresh-intent file now stays in the 'submitted' state after a
  successful rotation (it previously cleared it). It is only cleared by the
  account store via clearNousRefreshIntent() once mergeAccountCredential
  persists the rotated token.
- Replaying a token whose intent is 'submitted' OR 'uncertain' is refused up
  front (NousTokenError refresh_token_reused, terminal) — never blindly
  replayed, and without depending on the server's reuse detection.
- Network-level failure (server never saw the token) still clears the intent
  so a retry is safe.
- clearNousRefreshIntent is wired into the shared refresh orchestrator
  (src/oauth/index.ts) right after mergeAccountCredential; it is a no-op for
  non-Nous providers (they never write an intent).

Verified by a real execution probe (not just mocks): a rotation that obtains
the rotated token but crashes before persistence now makes the next replay of
the old token refused by the guard, with the intent present on disk.

Tests: nous-oauth 23/23 (adds 'rotated token obtained but not persisted
blocks replay', '200 unparseable body marks uncertain', 'network failure
replayable'); targeted suite 195/195. tsc + privacy:scan clean.

* fix(oauth/nous): fail-closed refresh-intent, hardened IO, shared-classifier terminal

Addresses the remaining CHANGES_REQUESTED findings from Wibias on PR lidge-jun#1397
(head after this: fail-closed end-to-end single-use refresh recovery).

1. Refresh-intent is now FAIL-CLOSED and reuses the repo's hardened config IO:
   - writeRefreshIntent uses atomicWriteFile + hardenConfigDir (owner-only
     0o700 dir) and THROWS on failure instead of swallowing it (refresh is
     refused rather than proceeding blind). readRefreshIntent treats any
     read/parse/permission error as 'uncertain' (replay refused), never as
     absent. clearNousRefreshIntent surfaces non-ENOENT failures.
   - Ambiguous fetch failures (timeout/abort/connection) now mark the intent
     'uncertain' instead of clearing it: dispatch may have occurred, so the
     submitted token must never be replayed.
2. Post-persist cleanup is wired into the correct coordinator
   (refreshGenericAccountWithLock, the actual Nous path) after a successful
   mergeAccountCredential; removed the misplaced call from the xAI path.
3. Shared terminal classifier now honors NousTokenError.terminal (so
   provider-classified invalid_token / insufficient_scope move the account to
   re-authentication instead of staying retryable).
4. Opt-in live test refreshes through the production, generation-aware,
   account-locked coordinator (refreshGenericAccountWithLock) instead of
   calling refreshNousToken + mergeAccountCredential outside the lock.
5. First normal refresh-wiring test now isolates OPENCODEX_HOME so it cannot
   leave durable intent state in the config tree.
6. Embedded-credential URL validation no longer echoes the raw (credential-
   bearing) URL in the thrown error.
7. NousTokenError no longer stores live credentials as an enumerable property;
   only the rotated refresh token is retained, via a non-enumerable getter
   (getRotatedRefresh), so structured logging/serialization cannot leak it.
8. Replay-guard test now proves fetch is never called (not just the error
   shape).
9. Provider docs (ja/ko/zh-cn) updated to 'eight' OAuth presets to match the
   English/Russian sources.

Verified by a real execution probe (not just mocks): rotation obtained but not
persisted -> next replay refused by guard; network failure -> fail-closed
uncertain (not replayable); insufficient_scope error does not leak credentials.

Tests: nous-oauth 23/23 (adds fail-closed network-failure, replay-guard
proves-no-fetch, non-enumerable credentials); targeted suite 195/195.
tsc --noEmit and bun run privacy:scan clean. Kept draft, no maintainer-sponsored.

* fix(oauth/nous): fail-closed refresh-intent schema, atomic HTTP failure, non-terminal local IO

- Validate persisted refresh-intent schema; corrupt/unknown state is treated
  as uncertain (replay refused), never absent. Only ENOENT means no intent.
- Classify HTTP refresh failures atomically: ambiguous 5xx/gateway responses
  leave the submitted token blocked (uncertain); only definitive 4xx client
  rejections clear the intent for a safe retry.
- Surface local durable-write/read/cleanup failures as a non-terminal
  RefreshIntentIOError so the coordinator does not mark a valid credential
  needsReauth for broken local persistence.
- Mark device-flow access_denied/expired_token as terminal consistently.
- Handle non-JSON successful device-code bodies with the clear validation
  error instead of a raw JSON parse leak.
- Redact raw values from malformed base-URL diagnostics.
- Align the opaque-token docstring with the JWT scope gate.
- Synchronize OAuth provider lists across en/ja/ko/ru/zh-cn docs.
- Add regression coverage for all safety contracts.

* test(oauth/nous): cover origin-only base URL normalization

* test(oauth/nous): make intent-write failure tests platform-independent

Planting a file at the intent-directory path made the guard read fail with
ENOTDIR on Linux (treated as uncertain -> terminal) before any write could
fail, so the test could not reach the non-terminal operational-error path.
Force atomicWriteFile to fail via a spy instead, deterministically on every
platform: the pre-dispatch write abort must surface RefreshIntentIOError,
never call fetch, and leave the account valid.

* fix(oauth/nous): fail closed on every ambiguous post-dispatch refresh outcome

A non-2xx response does not prove the single-use refresh token was not
consumed: 429 rate limits, unknown/custom 4xx, and gateway-generated
client-class errors can be returned after the remote side already processed
the token. Previously every 4xx cleared the durable refresh intent, which
made a possibly-consumed RT-A locally replayable.

Now every post-dispatch non-2xx response retains the intent as uncertain
(previously only 5xx did), so the submitted token stays blocked and a later
refresh is rejected before any fetch. The intent is cleared only after the
rotated credential is durably persisted. Pre-dispatch local I/O failures
remain distinct non-terminal operational errors.

Replace the invented 'safe 4xx' test with regressions proving HTTP 429 and
an unknown/custom 4xx both keep the old token blocked and reject a second
attempt before fetch (exactly one token-endpoint call).

* fix(oauth/nous): post-persist intent cleanup is best-effort; docs/live-test/modelDiscovery cleanups

- refreshGenericAccountWithLock: a failure to unlink the old-token refresh-
  intent file after mergeAccountCredential commits the rotation no longer
  fails the refresh or marks the account needsReauth. The stale intent keys
  the old token (no longer stored), so retaining it is safe; the failure is
  logged non-fatally with no credential material.
- Add coordinator-level regressions: the happy path persists RT-B and clears
  the RT-A intent (nousRefreshIntentBlocksReplay(RT-A) === false), and a
  forced cleanup failure still resolves with the fresh access token while the
  stored credential stays RT-B and the account is not marked needsReauth.
- Add the provider-level clear-after-persist regression in nous-oauth.test.ts.
- English providers doc: after a terminal Nous refresh failure, run
  'ocx login nous' to reauthenticate.
- Live test: correct the privacy wording (opt-in; credentials go only to the
  intended Nous endpoints; token values never printed) and parse the live
  catalog defensively so malformed bodies yield an empty list instead of a
  crash.
- Nous registry modelDiscovery: use path 'models' (resolves against
  effectiveBaseUrl to the same canonical /v1/models endpoint).

* docs(providers): sync Nous Portal details across ja/ko/ru/zh-cn

Add the missing ocx login nous command, the full
ous provider table row
(openai-chat adapter, inference endpoint, device-grant login, per-request
inference JWT, live paid/:free discovery, single-use rotated refresh tokens),
and the terminal-refresh reauthentication instruction to each translated
provider guide, matching the English source.

* fix(oauth/nous): preserve rotated RT-B on terminal refresh errors; trim live-test model ids

- refreshGenericAccountWithLock: when a terminal NousTokenError carries an
  already-issued rotated refresh token (e.g. access JWT lacks inference:invoke),
  persist RT-B generation-safely before forcing reauthentication. The unusable
  access token is never persisted as valid (empty placeholder, past expiry);
  RT-A's intent is cleared only after RT-B is durable (best-effort cleanup);
  persistence failure or a superseding concurrent generation never clears RT-A
  intent and never overwrites the newer credential; the account is marked
  needsReauth generation-safely and the caller receives OAuthLoginRequiredError.
- Live catalog test: reject empty/whitespace-only model ids (trim before accept).
- Coordinator regressions: RT-B preservation on insufficient_scope, RT-B
  persistence failure keeps RT-A intent blocking, superseded concurrent
  generation is not overwritten, cleanup failure after RT-B persistence keeps
  RT-B and marks needsReauth.

---------

Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
* fix: add per-model structured output opt-out

* fix: honor exact structured-output opt-outs

* docs(config): clarify exact structured-output opt-outs

---------

Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
…bute (lidge-jun#1407)

* fix(collaboration): scope catalog-state guidance to what we can attribute

`collectCodexAppServerCatalogState()` folds every app-server owned by the
current user into ONE global observation, and an inbound request carries no
sender PID or catalog fingerprint. So a stale process A makes the global state
`stale` even when the request came from a fresh process B, and `unknown` can be
reached by a process-enumeration failure that says nothing about any particular
server.

v2 guidance nonetheless injected

    do not set model or reasoning_effort overrides until Codex restarts

off that global observation. Two problems follow:

  * it prohibits options the active `spawn_agent` tool legitimately advertises,
    for a request that cannot be attributed to the stale process (lidge-jun#1395); and
  * the same text was emitted for `unknown`, asserting a cause that was never
    established and a restart that cannot clear a failed measurement (lidge-jun#1354).

For `stale` and `unknown`, withhold opencodex's own disk-derived claims —
preferred model, roster, fallback, custom guidance — and say nothing about
overrides, leaving the active tool schema authoritative. `fresh` and
`not_running` are unchanged.

The previous regression asserted that both states contain `do not set`, which
codified the scoping bug; it is replaced by coverage that a mixed stale/fresh
process set resolving globally to `stale` produces no blanket no-override
instruction for an unidentified request.

Documents the four-state guidance contract in the sub-agent surface guide,
including why `unknown` is not a synonym for `stale`.

Verified:
  bun test tests/multi-agent-compat.test.ts        -> 43 pass, 0 fail
  bun test tests/codex-app-server-processes.test.ts -> 30 pass, 1 skip, 0 fail
  bun x tsc --noEmit                                -> clean

Fixes lidge-jun#1354
Fixes lidge-jun#1395

* Update sub-agent-surface.md for clarity on states

Clarify the distinction between 'stale' and 'unknown' states in the documentation.

---------

Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
* fix(codex): honor split SQLite home

* fix(codex): fail closed on unreadable SQLite config

* fix(codex): fail closed on invalid SQLite config

* fix(codex): preserve residue classification boundary

---------

Co-authored-by: Ingwannu <ingwannu@users.noreply.github.com>
…ider

Register `daybreak-red-latest` and `daybreak-blue-latest` on `openai-apikey`.

The alias ids are the stable contract: OpenAI repoints them at newer snapshots
over time (red -> gpt-5.6-cyber, blue -> gpt-5.6-sol as of 2026-08-11), so
registering the alias inherits future model swaps, while a pinned snapshot id
would silently go stale. No snapshot id enters the registry.

Both pages mark `v1/chat/completions` Not supported and `v1/responses`
Supported, so the rows go only on `openai-apikey` (adapter openai-responses,
api.openai.com). The Codex-login `openai` provider is excluded: Daybreak needs
separate provisioning and is absent from the pinned upstream snapshot, so a row
there would advertise something that path cannot route. `defaultModel` stays
`gpt-5.5` — a provisioned-only model is never a default.

Metadata per the official pages: red 400,000 context / 272,000 max input, blue
1,050,000 / 922,000, both text+image with reasoning-token support.

`modelReasoningEfforts` carries an explicit `[]` for both. Neither page
publishes an effort ladder, and omitting the key would NOT mean "no ladder":
`configuredReasoningEfforts` returns undefined, and `applyReasoningLevels` then
falls back to the full routed ladder, advertising efforts the models never
documented. `noReasoningModels` is also wrong here — both document reasoning
tokens, so these are reasoning models with no *selectable* ladder.

Pricing is `verified-derived`, not `verified`: the pricing table has no
`daybreak-*` rows, only the snapshots', so an alias price is a mapped base price.
That status is also what keeps the `estimated` marker on, which matters more than
usual because a repoint can change the price. Long-context tiers follow the
published table rather than the family default: blue gets the 272,000-exclusive
tier (its snapshot publishes a full long row), red gets none (its long columns
are all "-"). Blue's tier is one explicit `openai-apikey` entry rather than a
member of the shared list, which expands across both OpenAI providers and would
otherwise mint a Codex-login tier that cannot exist.

Verification: bun run typecheck clean; bun run test 10931 pass / 0 fail.

Plan and evidence: devlog/_plan/260811_260811-gpt56-cyber-model/
…idge-jun#1435)

* fix(catalog): apply providerContextCaps.openai to native OpenAI rows

Native OpenAI catalog rows ignored providerContextCaps.openai: the fixed
native context overrides in metadata.ts stayed at 372k while routed models
were capped. Thread the openai provider cap through the catalog entry
builders (finishUpstreamNativeEntry, deriveEntry, buildCatalogEntries,
mergeCatalogEntriesForSync), the runtime native metadata accessors
(nativeOpenAiContextWindow, nativeModelRows), and routing capability
evidence so the catalog, management rows, and proxy routing agree.
Preserved native rows without a hardcoded override (e.g. gpt-5.4-mini) are
capped the same way, and auto-compaction follows the capped window.

The 372k native value remains the default when no cap is configured.

Closes lidge-jun#1430

* fix(catalog): cap live Codex discovery rows
…ive-test account read (follow-up to lidge-jun#1397) (lidge-jun#1450)

* docs(providers): table-safe Kiro install pipes in translations; fix live-test account read

- ja/ko/ru/zh-cn kiro rows: replace literal pipe characters in the Kiro CLI
  install pipelines with table-safe &#124; entities so the markdown tables
  render correctly.
- nous-oauth-live.test.ts: read the refreshed account with
  getAccountCredential('nous', accountId) instead of passing an excess
  accountId argument to getCredential (fixes the type error).

* fix(oauth/nous): address CodeRabbit + review findings on the follow-up

- preserveNousRotatedRefresh returns the exact generation it wrote, removing
  the post-merge getAccountCredential re-read so a concurrent writer cannot be
  marked needsReauth (TOCTOU). Rethrow OAuthMutationBusyError unchanged so the
  caller can retry.
- parseTokenPayload: a missing access_token is now a terminal NousTokenError
  (invalid_token) instead of a plain Error.
- Live test: read the store row id via getAccountSet(...).activeAccountId and
  pass rowId! to both refreshGenericAccountWithLock and getAccountCredential
  (the row id is a SHA-256-derived hash, not the JWT sub).
- Docs: escape the Kiro install pipes (&#124;) in the English providers table
  too, and add command-code to the oauth 'Used by' list across all five locales.
- Regression: persisted-branch TOCTOU test (concurrent writer not marked
  needsReauth).

* fix(oauth/nous): code/robustness fixes from CodeRabbit outside-diff review

- jwtExpiryMs: only accept an exp within a plausible now-relative window,
  falling back to expires_in for out-of-range claims; clamp skew-adjusted
  expiry to non-negative. Add a dedicated DEFAULT_ACCESS_TOKEN_TTL_MS so the
  device-flow window is not reused as the access-token fallback lifetime.
- pollForToken: tolerate transient transport errors (timeout/DNS/connection)
  until the device deadline; only genuine cancellation aborts early.
- writeRefreshIntent: re-apply owner-only 0o700 on an existing intent dir.
- resolvePortalBaseUrl docstring: stop claiming Hermes host-allowlist parity.
- parseTokenPayload: device-login missing refresh_token is invalid_token, not
  refresh_token_reused; missing access_token is now a terminal error.
- index.ts: anchor nous defaultRefreshPolicy explicitly to lazy-only.
- Live test: import NOUS_INFERENCE_BASE_URL instead of the hard-coded URL; use a
  structural refresh-only def type instead of the non-exported OAuthProviderDef.
- Regressions: implausible exp falls back to expires_in; device-login missing
  refresh_token is invalid_token.

* fix(oauth/nous): fail closed on refresh-intent hardening, honor device-flow deadline, classify missing access_token as terminal

* docs(providers): render Kiro install pipes as visible | in all locale tables

A bare &#124; entity inside a code span is emitted literally by Astro's
markdown processor, so the Kiro CLI install commands showed the escaped
text instead of a pipe. Move the pipe out of the code span so it renders
as a visible | between the two command fragments, keeping the markdown
table intact across all five locales.

* fix(oauth/nous): enforce device-flow deadline on every poll path and normalize null token bodies

CodeRabbit follow-up on the deadline cap: authorization_pending and
slow_down still slept the full interval and a delayed success response
could return credentials after the deadline. Route every retry through a
deadline-aware sleep helper and recheck the deadline after each fetch.
Also normalize a valid-JSON null response body to an empty object so a
successful-but-null payload raises the terminal invalid_token
NousTokenError instead of a raw TypeError. Add a regression test for the
null-body case.
…#1444)

* refactor(cli): move CLI head dispatch into src/cli/root.ts

Phase 1 of the CLI deepening: version/help early exits, the ocx ready
pre-parse (exit 64 before any preflight), and the bounded Codex-shim
auto-restore preflight move from src/cli/index.ts into a new pure module
src/cli/root.ts. index.ts now awaits runCli() and dispatches via its
existing switch; behavior is preserved byte-for-byte.

- parseCliHead is pure and unit-tested (tests/cli-head.test.ts)
- P1 source-guards in tests/cli-ready.test.ts relocate to root.ts
- cli-head/cli-ready/cli-help: 74 pass; typecheck green

* test(cli): cover the trailing help alias in parseCliHead

CodeRabbit finding on lidge-jun#1444: the help-flag test covered --help and -h
after position 0 but not the bare 'help' token, which hasHelpFlag also
matches.
…s (resync) (lidge-jun#1440)

* feat(i18n): add Traditional Chinese (zh-TW) across GUI, README, and docs

Add a full Taiwan Traditional Chinese locale so Chinese users can pick
Simplified or Traditional instead of a single generic "中文" option.

- GUI: new zh-TW dictionary, locale wiring, uptime/token/status formatting
- README: new README.zh-TW.md and language switcher links
- Docs site: zh-tw locale, sidebar labels, landing/frontier copy
- Tests: include zh-TW in locale coverage lists
- Prefer 供應商 for provider wording in Traditional Chinese surfaces

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(i18n): address zh-TW PR review findings

Point zh-tw docs links at /zh-tw, add missing grok-build and
windows-memory pages, polish GUI/README/frontier copy, and align
Cursor nativeLocalExec plus headless CLI docs with English.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(i18n): close zh-TW content parity gaps from Codex review

Backfill Auth mode and Desktop sections, account selection, external
provider managers, missing config fields, debug stream labels, and
align the README sidecar bullet with English.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(i18n): address residual zh-TW CodeRabbit findings

Align how-it-works sidecar steps with multi-backend selection,
fix grok-build manual-recipe heading anchor, escape bare pipes in
the headless CLI table, and use a host-only openai-responses baseUrl
example so /v1 is not doubled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(i18n): resolve merge conflict markers and resync zh-TW keys

Keep zh-TW sidebar translations while adding the new opencode guide
entry from upstream, and bring the Traditional Chinese dictionary to
full key parity with en after the dev merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(i18n): add zh-TW opencode guide page

Translate the EN-only guides/opencode page so the zh-TW sidebar entry
resolves, addressing the post-merge CodeRabbit finding.

* docs(zh-tw): translate 14 remaining reference and guide pages

Complete zh-TW docs parity with English source (33/33 pages):
- reference/cli/agents, lifecycle, providers-accounts
- reference/configuration/providers, server
- reference/management-api, proxy-formats
- guides/combos, image-bridge, pi, video-bridge
- getting-started/for-agents
- reference/configuration/agents, routing

All pages render Traditional Chinese with lang="zh-TW"; docs-site
build produces 247 pages; privacy:scan green.

* docs(zh-tw): add Traditional Chinese GUI screenshot evidence

Captured the real dashboard surface (providers workspace) with the
zh-TW locale active — nav shows 儀表板/供應商/模型/子代理/整合, language
picker shows 繁體中文, and the providers copy is fully translated.

* fix(i18n, docs): tr locale parity, real-mount language switch test, Taiwan terms, docs drift resync

- sync-locale-keys.mjs / locale-parity / en.ts header now include tr
- zh-tw-language-switch test mounts a real LanguageProvider surface and
  flips locale through the actual Select UI (same DOM, persistence, lang)
- zh-TW.ts/README zh-TW term fixes: 獲取→取得, 請求頭→標頭, provider 代理→供應商代理
- README language rows gain Türkçe/繁體中文 cross-links
- routing.md fully retranslated (policy profiles, exact account selectors,
  analytics, migration)
- providers.md gains Cockpit import, OAuth reliability, Kiro import, A6API,
  pool capacity, rate limits; presets 50→79
- lifecycle.md gains ocx ready + ocx service repair

* docs(zh-tw): resync 16 pages against current English source

Deep content-sync pass found and fixed real drift beyond file parity:
- configuration.md, cli.md: old full-text pages rewritten as index pages
  matching the reduced English source (4/4 sections each)
- contributing.md: +Branches/Pull requests/Project maintainers/preset evidence
- web-dashboard.md: +Sign-in/star-consent sections
- server.md: +local clients that cannot receive the token
- cli/providers-accounts.md: +ocx account priority/main
- sub-agent-surface/combos/claude-code/grok-build/opencode/codex-integration:
  missing sections added
- quickstart: 79 presets, current release wording
- codex-app-models: desktop remote-allowlist limitation + current coverage
Verified: docs build 265 pages, 34/35 files match en section count (sole
delta is an empty en heading), no stale v2.7.1/50-preset strings, no bare
internal links missing /zh-tw/.

* chore(assets): remove review-only zh-TW screenshot from the repo

The screenshot exists only as PR-description evidence; keep review
artifacts out of the product repository per review feedback.

* docs(assets): restore zh-TW dashboard screenshot as a docs asset

Screenshot belongs in assets/ alongside dashboard.png and the other
locale-neutral UI captures: the zh-TW providers workspace is the
rendered evidence for the localization, referenced from the PR
description and suitable for the docs site.

* docs(zh-tw): address CodeRabbit review findings on markdown and content

- installation.md: blank line after fenced block before admonition
- image-bridge.md: fix #configuration anchor to the translated #設定
- pi.md: blank line between fence and admonition close
- adapters.md: translate page title (Adapters → 轉接器); blank line
  before subsection heading
- windows-memory.md: sync observed-memory (max of RSS/external/arrayBuffers)
  and use ocx service repair for existing services
- codex-app-models.md: natural passive wording (可供切換)
- README.zh-TW.md: drop duplicated 請在

Other CodeRabbit findings were reviewed and intentionally left aligned
with the English source (table pipe escapes, v1/base/v2 labels, xAI
pricing note, TODO comments, h3 README hero) or are obsolete since the
pages were rewritten.

* docs(zh-tw): sync 5 pages with the latest dev docs changes

- providers.md: Nous Portal OAuth preset (8 OAuth presets), ocx login nous,
  nous table row + refresh note
- sub-agent-surface.md: catalog-trust section (fresh/not_running/stale/unknown)
- claude-code.md: thinking disabled maps to reasoning:{effort:none}; tool_choice
  hosted WebSearch entry
- reference/configuration/providers.md: noStructuredOutputModels field
- reference/proxy-formats.md: structured-output translation paragraph

Re-merge onto dev 0c17052 (was 38 commits behind; gate blocked readiness).

* docs(zh-tw): clarify catalog-trust table wording (CodeRabbit)

stale/unknown states do not add or overwrite opencodex-authored guidance,
consistent with the withholding paragraph below.

* test(zh-tw): restore browser global descriptors exactly

* docs(zh-tw): resync Codex integration guide with dev

* docs(zh-tw): resync providers guide with current dev

---------

Co-authored-by: letr1n1ty <letr1n1ty@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…jun#1446)

* refactor(cli): add command registry as single metadata source

Phase 2 of the CLI deepening: command names, aliases, usage, summary,
and details move from src/cli/help.ts into src/cli/registry.ts
(CLI_COMMANDS + findCommand). help.ts becomes a thin renderer over the
registry; behavior is unchanged.

- 48 visible entries in original order, plus 6 hidden __* entries
- alias pairs: init/setup, restore/eject, uninstall/remove, models/model
- exact-name-wins lookup keeps alias-name entries' own help text
- tests/cli-registry.test.ts pins switch-case <-> registry parity
- all 48 help outputs byte-identical; typecheck green

* test(cli): require every registry entry name directly in the switch

Addresses CodeRabbit finding on lidge-jun#1446: the previous assertion let an
alias case satisfy a missing canonical case, so dropping e.g. 'init'
while 'setup' remained would pass. Require each entry.name directly in
caseSet.
* refactor(cli): registry-driven command dispatch module

Phase 3 of the CLI deepening: extract the command switch out of
src/cli/index.ts into src/cli/dispatch.ts as a registry-driven runner
table. index.ts becomes a thin main that passes its local lifecycle
helpers through CliDispatchDeps; aliases resolve via the registry alias
map.

- dispatchCommand(head, deps) replaces the 61-case switch
- behavior preserved: restore/sync/sync-cache/claude/route/integration/
  health/ready/gui/codex-shim/update runners match the original bodies
- source-level tests migrated to read dispatch.ts
- typecheck green; CLI suite 211 pass (4 known pre-existing failures)

* test(cli): migrate app-server sync wiring test to dispatch.ts

Phase 3 moved the command switch into src/cli/dispatch.ts as runner
keys, but tests/codex-app-server-processes.test.ts still sliced the old
case labels out of src/cli/index.ts. Update it to read the sync,
sync-cache, v2, and gui runner bodies from dispatch.ts (using deps.args),
which restores the lidge-jun#476 sync/sync-cache app-server-wiring assertions.
…jun#1455)

* refactor(cli): normalize process exit through dispatchCommand

Phase 4 of the CLI deepening: dispatch runners now RETURN exit codes
instead of calling process.exit/setting process.exitCode directly.
dispatchCommand aggregates and returns the final code; index.ts performs
the single process.exit(await dispatchCommand(...)).

- CommandRunner becomes (deps) => Promise<number>
- unknown command -> 1, help/--help/-h/undefined -> 0
- restore/route/integration/claude/ready/health runners return their codes
- behavior preserved: smoke tests (version 0, help nosuch 1, ready
  invalid 64, sync --help 0) pass; cli-ready runner assertion updated

* fix(cli): preserve handler-set exit codes in dispatch runners

Phase 4 exit normalization made runners return 0, but handlers passed via
deps (handleStart/handleStop/handleStatus/handleEnsure/handleUninstall/
handleRecoverHistory/handleProxyRestart/handleTrayProxyRestart) and
runInit set process.exitCode internally. The hardcoded return 0 overrode
those codes (e.g. ocx init exited 0 instead of 1 on stdin EOF). Return
process.exitCode ?? 0 so the handler-set codes are preserved.

* fix(cli): propagate ready result through dispatchCommand

Addresses CodeRabbit finding on lidge-jun#1455: handleReady was typed
Promise<never> and called process.exit(await runReady(args)), so a valid
'ocx ready' terminated below the dispatcher instead of returning its code
to the single top-level process.exit(await dispatchCommand(...)).

- handleReady now returns Promise<number> and returns runReady(args)
- CliDispatchDeps.handleReady typed Promise<number>
- cli-ready source test updated to assert the return-through-dispatch
  behavior (body returns runReady, no process.exit); invalid-args 64
  branch still covered
…1456)

* refactor(cli): fold internal-dispatch and pin help banner

Phase 5 of the CLI deepening:
- fold src/cli/internal-dispatch.ts into src/cli/dispatch.ts as three
  explicit runner entries (__tray-start, __tray-restart,
  __startup-health) that use the injected deps directly; delete the
  module and its dedicated test
- add banner-coverage test to cli-registry.test.ts and fix the drift it
  caught: printUsage now lists the previously-missing visible commands
  (route, logs, api-key)

Behavior preserved: typecheck green; 144 pass / 4 known pre-existing
environmental failures; smoke tests (version 0, help nosuch 1, ready
invalid 64, --help header+new lines) pass

* fix(cli): return tray-start status and tighten banner coverage

Addresses two CodeRabbit findings on lidge-jun#1456:
- __tray-start now returns 1 when handleTrayProxyStart() returns false
  (could not make the proxy live) instead of always reporting success
- banner-coverage test now matches 'ocx <canonical-name>' at the start of
  a banner line (escaped) rather than any substring; adds the previously
  uncovered usage/storage/memory banner lines
* ci: add fresh-process Bun test batching

* ci: run Linux test shards in fresh-process batches

* test: preserve api-usage isolation contract

* test: preserve storage-policy isolation contract

* ci: bound Bun test batches with timeouts

* ci: isolate unstable Bun batches per file
@luvs01
luvs01 force-pushed the codex/fix-global-reasoning-replay-cache-leak branch from d7b98d8 to 4e22f98 Compare August 11, 2026 10:22
@luvs01 luvs01 closed this Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

aardvark bug Something isn't working codex

Projects

None yet

Development

Successfully merging this pull request may close these issues.