Repository navigation
[WRONG BRANCH] release: promote frozen tip 7b3bb7a257 to main as 2.97.0 - #8134
Conversation
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ccounts (#8088) * fix(anthropic): keep inactive shared quota meters Treat is_active as the binding-meter marker for shared five-hour and weekly usage, including routing and cooldown recovery. When legacy five_hour is null, only an explicitly active finite session establishes a meter. Keep scoped model limits active-only. * test(anthropic): keep routing fixtures' weekly resets in the future
…adroom (#8092) * fix(codex): place round-robin conversations on accounts with quota headroom Round-robin rotated new Codex conversations by count alone, so a fresh conversation could bind to an account already at 95% and then move to a cold prompt cache when that account ran out. New placements now skip accounts at or over their effective autoSwitchThreshold while any eligible account is under it, using the hasCodexQuotaHeadroom score fill-first and the priority tier already use. When every account is over, rotation covers all of them as before. Bound conversations, failover alternates and the lineage first-placement hint keep their existing behaviour. Docs: document the Codex rule, and correct the Claude quota strategy description, which keeps the active account until it reaches autoSwitchThreshold before moving to the least-used account. * fix(codex): keep an operator's manual selection in round-robin placement Manual selection seeds the round-robin ring so the next placement lands on the chosen account. The headroom preference dropped an over-threshold selection from the candidates, which cleared that seed. Keep the scope's unspent manual selection in the list; once a dispatch on it succeeds the selection is spent and the headroom preference applies again. Docs: translated quotaWindow rows now say per-account weekly bars come from usage probes or observed response headers, matching English. * fix(codex): honor a manual selection on the Reserve ring too Manual selection seeds the shared ring and each independent quota-scope ring, but recorded the one-shot preference for the shared scope only, so the round-robin headroom preference dropped an over-threshold selection from the next Reserve placement. Record the one-shot per seeded scope and clear it with the seeds on release. Only the shared one-shot guards the shared active cursor; a successful dispatch in a scope spends that scope's one-shot. * test(codex): assert a shared success leaves the Reserve one-shot unspent * docs(codex): describe the per-scope one-shot lifecycle of a manual selection under round-robin
…ion affinity (#8080) (#8096) * fix(codex): honest affinity labels and opt-in request-owned conversation affinity (#8080) A Codex request carrying the caller's own ChatGPT bearer in Pool mode got no affinity key, so round-robin re-picked every turn and the request log still reported new_bind/healthy without any stored binding. - Affinity decisions are checked when selection returns: new_bind/rebound stay only if the key's entry names the selected account; otherwise the row reads unbound (request_owned / no_conversation / original reason) or cleared. - codexPool.requestOwnedAffinity (default off) gives request-owned traffic a process-local key scoped by an HMAC of the exact Authorization value and the conversation key. Final auth and both previews share one derivation; fixed accounts and Reserve get none; lineage is never recorded. - Keyed caller-owned main contexts carry the key and act as issuer __main__, so Pool<->main moves (incl. retry and compact alternates) scrub carried state. - Request-owned main pin logic moves byte-for-byte to request-owned-main-pin.ts. * fix(codex): scope conversation-state issuers per bearer and fix review findings (#8080) - Request-owned Pool and caller-owned main contexts carry a separate conversationStateKey (bearer-scoped HMAC), never passed to routing. Issuer records no longer borrow the stored-credential raw conversation key, and Pool<->main moves scrub carried state even with the opt-in off; the manual main pin path keeps the issuer key. - Caller-main fallbacks report what happened to main (detour/unbound), not the other account's decision. - Previews derive the key with each candidate model; recovery recomputes lineage from its own headers and ownership. * test(codex): drive real preview callbacks; document issuer retention (#8080) - Reserve and recovery preview regressions exercise the actual subagent fallback and encrypted-task recovery closures instead of source text. - Structure doc: manual selection keeps conversation-state issuers; stored credential traffic keeps its shared issuer namespace, unchanged. - Response-state store is cleared after the alternate-main test. * fix(codex): release a caller-owned main binding after its bearer is refused (#8080) With codexPool.requestOwnedAffinity on, a conversation bound to caller-owned main stayed there after the caller's bearer returned 429s, because main contexts bypass Pool outcome recording. Quota, credential and final transient refusals now release that caller-scoped binding (ordinary and model-detour entries) and carry the release reason to the next selection, without touching shared physical-main health, quota or cooldown. Covers regular, compact, streamed-terminal and WebSocket terminal paths. Docs: guide documents issuer-based scrubbing with the option on or off; the structure doc keeps only the present-tense persistence rule; the devlog unit is reduced to a present-tense record. * fix(codex): fence caller-main releases to selection-time bindings (#8080) - A delayed refusal releases only the binding entries captured when main was selected, so a newer binding from a later turn of the same conversation survives. - Only a caller-owned main carrying a request-owned key inspects a 5xx body, so unkeyed main requests are never delayed by the quota body reader.
… account (carry #8028) (#8094) * fix(claude): bind header-less Messages bridge turns to one Codex pool account The Messages bridge replays each Claude turn on an internal /v1/responses request. Without caller session-id and thread-id, codexPoolAffinityKey is undefined, so the Codex pool never binds the turn and round-robin with session pinning moves every turn to the next account. The bridge now resolves a private pool identity (caller session alias, else the metadata session, else x-claude-code-session-id; derived values are scoped to the admitted principal for remote callers) and passes it out of band as HandleResponsesOptions.claudeBridgePoolSession. Only Codex account-selection headers complete session-id and thread-id from it: credential-domain headers, both 401 refresh materializations, and the route-independent pool key in request prepare. Replay headers stay untouched, so request logs, reasoning replay, Go lanes, adapter fingerprints, Anthropic passthrough and non-Codex routes are unchanged. Spawn turns, declared parents, caller thread ids and complete caller pairs keep their behavior. Carries #8028. Co-authored-by: vibewriter-hand <337401476+vibewriter-hand@users.noreply.github.com> * test(claude): isolate lineage and prove parent-account inheritance in bridge pool dispatch * docs(pool): note Claude Code session binding for Codex pool strategies * fix(claude): scope bridge pool aliases per principal and keep the pool identity off the wire Remote callers' session_id/session-id aliases now pass through the same principal scoping as the metadata and Claude session header values before they name a Codex pool session; no principal means no identity. The completed session-id/thread-id are now given only to Codex account selection (final and alternate-account) and selection previews. First-send, 401 refresh and alternate-account retry headers are built from the uncompleted request headers, so the upstream wire is unchanged from dev. Co-authored-by: vibewriter-hand <337401476+vibewriter-hand@users.noreply.github.com> * fix(claude): key bridge pool identities with a process-local secret A complete caller-supplied session-id + thread-id pair reaches Codex selection unscoped, so a synthesized pool identity must not be computable by callers. Every identity the bridge names (caller alias, metadata session or Claude session header) is now an HMAC under a process-local key over its admission scope (loopback, or the remote principal), never the raw value or a public hash. The identity stays in the process: replay and upstream headers are built without it. Co-authored-by: vibewriter-hand <337401476+vibewriter-hand@users.noreply.github.com> * test(claude): observe the alternate-account pick's selection key in bridge pool failover * docs(structure): name the metadata-derived cache key as the bridge identity source --------- Co-authored-by: vibewriter-hand <337401476+vibewriter-hand@users.noreply.github.com>
* wip(codex): lease releasing hooks, owned affinity paths, lineage anchor observer, plan unit * wip(codex): durable affinity store module * wip(codex): harden affinity store (readability, release vs home retirement, scope exhaustiveness, gap tests) * wip(codex): wire durable affinity into thread affinity and account-change state; restart tests * docs(codex): durable pool affinity contract and restart wording * test(codex): restart matrix for durable pool affinity * test(codex): remaining restart matrix cases (compact turn-state case currently red) * fix(codex): keep row keys for persisted deletes, durable touches for unknown issuers, compact turn-state scrub * fix(codex): reconcile live affinity on new lease, bounded and pruned store, file refusal for moved unknown issuers, gateway compact tests * fix(codex): bounded flush under the size cap, scope-aware adoption, eager startup prune * fix(codex): touches before chunk prunes, full scope enumeration, prune after failed flush, passive-safe lease hooks * docs(codex): describe affinity pruning as attempted, with its exceptions * fix(codex): apply touches before every chunk prune * chore: trim trailing blank lines * fix(codex): address CodeRabbit review — unscoped adoption marker, relative test timestamps, acquired-hook and restart-eligibility docs * fix(compact): rescrub turn state on 401 replay and on caller-owned main alternates * fix(codex): keep request-owned keys out of the durable store; align with manual-switch issuer retention; memory wins after a lease gap; persist in unowned homes * fix(codex): replay lease-gap changes instead of rewriting the store; conditional restart wording; French grammar * fix(codex): fail closed after a lease gap instead of replaying; carry row keys across lease changes * fix(codex): keep carried row keys past a failed reopen; document the post-release limit * docs(structure): state that affinity rows store the local pool account id
* Support outer request log finalization * Add Ollama protocol codec * Suppress protocol trace for internal chat requests * Extract admitted HTTP turn refusal ownership * Add Ollama route eligibility preflight * Coordinate native ingress final request logs * Register native ingress tests in the layout map * Serve Ollama-compatible inbound routes on the data-plane listener * Document the Ollama-compatible inbound surface * Keep returned tool call ids when an Ollama client replays them * Add an Ollama-compatible clients guide * Commit a cancelled NDJSON stream row during terminal settlement * Recheck client cancellation after settlement and reject data after the Chat terminator * Register the Ollama inbound tests in the layout maps * Note that Ollama success follows Chat settlement * Keep non-Ollama routes synchronous through the dispatcher and read the moved refusal wrapper * Keep upstream error text out of Ollama responses and rows, and carry workflow identity * Generate Ollama response headers instead of copying them * Map Ollama refusal codes to constants, answer fenced Ollama paths natively, and look up one show route * Clarify the Ollama missing-finish rule for streamed JSON fallback
…ut/1) (#8100) * feat(sync): add fan-out execution primitives * feat(sync): fan out committed Home catalog to linked children * fix(sync): keep version probe refusals machine-readable * docs(devlog): plan for Home sync fan-out to linked Children (C2) Audited over eight rounds; records the contract, the per-Child execution and classification rules, isolation bounds and the decision to never restart a Child's Codex from the Home. * fix(sync): frame fan-out output and reject oversized probes * fix(sync): frame platform probes and reject incomplete output * test(sync): point sync source oracles at the moved sync runner The sync runner moved from src/cli/dispatch.ts to src/cli/sync-command.ts; the owned-client refresh and stale app-server wiring guards still read the old file and failed in the full Ubuntu shards. They now slice runSyncCommand from sync-command.ts; sync-cache keeps reading dispatch.ts. * fix(sync): secure fan-out capture and align references * docs(sync): state when Home fan-out exits 6
…hards for the new affinity tests (#8102) * fix(codex): refuse links at SQLite store and sidecar names before creating them Windows creates a new file through a symbolic link at its name, so the affinity store's create-then-lstat order created the link's target before refusing the path (windows 11/11, run 41437409412). The spend-ledger owner had the same order. SQLite's Windows VFS also opens -journal/-wal/-shm by name and follows a link there. src/lib/private-sqlite-path.ts checks the database and its sidecars before anything is created (and, on Windows, the reparse-point attribute), and the affinity store re-checks sidecars before every write transaction. * fix(codex): re-check the affinity database identity and link count before every write A hard link added to the open database is refused before the next write transaction, as a planted sidecar link already was. Docs now separate open-time from per-transaction checks and state that Windows relies on the hardened ACL rather than an ownership comparison. * test(layout): register private-sqlite-path in the file-identity shard Keeps scripts/test-layout/layout.json under the file-size ratchet, and narrows the clean-flag wording to the poisoned and failed-final-flush cases. * ci(test): rebalance shards for the new affinity tests and seed restart tests once Linux test 1/4 hit the 15-minute job timeout in run 41437409412: seven new affinity and bridge files had no recorded duration and weighed the median. Both duration tables are refreshed from that run with scripts/ci/test-durations.ts (the only run that measured them; the source lines record the cancelled shard and the retained Windows rows). The two affinity restart files spawned a full seeding worker before nearly every case. They now seed one template home per file and copy it, rebinding the ownership root; clean-restart, crash, unowned-home, two-home and one unknown-issuer handler case keep real seeding. Locally 19.2 s -> 13.2 s. * fix(lib): gate the Windows reparse probe on the real platform Tests replace process.platform with win32 to drive Windows branches on POSIX; the native GetFileAttributesW query must follow the operating system the process runs on, or those tests see every owner database refused (tests/server/server-stop-config-hardening.test.ts). * fix: keep Windows ARM64 startable and compare store identity as BigInt Bun on Windows ARM64 cannot execute bun:ffi, so the reparse attribute query failed closed there and the spend-ledger owner could never be acquired. On ARM64 the check relies on lstat, which reports symbolic links, junctions and app execution aliases as links; the docs state the narrower coverage. The affinity store compares database identity with BigInt stats through sameFileIdentity: NTFS file IDs exceed Number.MAX_SAFE_INTEGER.
…hestrated/1) (#8103) Adds contract update.orchestrated/1 for external orchestrators (GOAT). - ocx update --plan --json: read-only, passive plan (ocx.update.plan/1) judged against the attested install serving the live runtime; refuses CLI/serving mismatch, desktop-app, source, mise and unknown installs; planToken, updateInFlight, mutationBoundMs. - ocx update --yes --json [--tag|--version] [--expect-plan] [--not-after]: non-interactive apply (ocx.update.result/1), exact version pin in npm, pnpm and Bun lanes; fence order sync/update mutex, ownership lease, in-flight marker with time fence, then deadline, plan-token and attested identity checks; supervised installers; exit 0/10/11/12/64 from installer outcome plus passive tree and runtime verification. - ocx sync and legacy ocx update share the sync/update mutex (sync exit 7 update-in-flight, 1 sync-in-progress; --json refusals emit one fan-out document); the launcher holds it across Desktop handoff with a delegate pid. - ocx capabilities --json gains top-level contracts; --contracts --json prints only the contract list (sync.home-fanout/1, update.orchestrated/1). Design and audit record: devlog/_plan/261102_ocx_update_orchestrated/010_plan.md.
…ce (#8104) * Normalize upstream error details in request logs and Retry-After advice * Update request-log and quota observation tests for normalized error details * Add regression tests for normalized error details and client retry advice * Document proxy-owned request-log error detail and client retry advice * Project final request-log rows before any writer and keep native evidence through classification * Keep long account-cooldown retry advice and the Ollama retry bound unchanged * Leave successful output-limit stops without an upstream condition * Keep proxy-written failure sentences, update row expectations and user docs * Format every computed client Retry-After and keep local diagnostics out of upstream conditions * Log combo hop failures by classified condition only * Record closed failure detail, bounded tiers and model identities, and normalized response metadata headers * Normalize response metadata headers by exact lookup and integer limits Header normalizers are selected from a Map and return primitive strings only, so names matching Object.prototype members are never relayed. Rate-limit counts are rebuilt as bounded non-negative integers and resets as whole seconds (or whole milliseconds below one second), clamped to eight days. Also: the hydration projection bounds service tiers itself; Retry-After accepts RFC 850 and asctime dates read as GMT; condition classification reuses the definite context-overflow verdict (moved to a dependency-free module), treats usage_limit_exceeded and 1308 as billing exhaustion, and is captured where the Messages passthrough parses the error; the combo hop warning passes the error type; dashboard docs note that older usage.jsonl rows are dropped on read. * Rebuild WebSocket response metadata from proxy-owned values Response metadata relayed to clients no longer copies upstream values. The turn-state token is replaced by a random proxy handle that is mapped back at request ingress (HTTP header, WebSocket upgrade and frame client_metadata). Request ids, the models etag and quota limit labels are not relayed, and quota windows are limited to the known limit families. The native codex.rate_limits frame is rebuilt from an explicit schema with bounded numbers and closed labels, and the upgrade inclusion header keeps its presence with a proxy-owned value. The error-detail boundary test polls for its live and persisted rows and asserts they are non-empty before checking for absent values. * Bind turn-state handles to the send and keep only known model ids Turn-state handles are bound at issuance to the admitted caller, the provider and the outbound account, and are redeemed only at the outbound send after admission and routing, when the send has the same binding; otherwise the value is removed and a fixed service-log flag is written. Each request and WebSocket frame records its admitted caller in a request-scoped slot. An upstream-reported model (relayed model headers, served and resolved models in request-log rows) is kept only when it equals a model id the request or a bundled catalog already names, and the proxy's copy is used. The rebuilt codex.rate_limits frame omits the credit balance, which stays available as the proxy-formatted header. * Apply the known-model rule inside passthrough header sanitization sanitizePassthroughHeaders now keeps openai-model and x-codex-safety-buffering-faster-model only as a model id the proxy already knows, so every passthrough branch (redirects, error statuses, JSON answered as JSON or re-framed as SSE) relays the proxy's copy or nothing. Known ids for a passthrough also include the models configured for the selected provider and its cached discovered models, so a provider-owned hint naming one of them is kept. Adds end-to-end tests that obtain a turn-state handle from a mock Codex WebSocket upstream and redeem it over HTTP and WebSocket, including two concurrent API-key callers. * Do not trust live-discovered model ids for relayed model headers Ids from a provider's live /models discovery come from the upstream itself, so they no longer count as known models when relaying openai-model or x-codex-safety-buffering-faster-model. Trusted ids stay the route's own ids, the bundled catalogs and the models configured for the provider. Adds a regression test that seeds discovery through the real discovery path and checks that both headers and the request-log fields drop the value. * Exclude preset-selected models from trusted passthrough model ids A model preset fills selectedModels from the discovered catalog, so it is not a user-declared source. Passthrough model headers now trust route ids, bundled catalogs, and the provider models, retainModels and defaultModel only.
…fan-out test fixture (#8106) * fix(link): keep Windows containment refusal at 126 and correct the fan-out test fixture #8100 packed every Windows remote command and inflated it before the containment helper existed, outside any try. A Child whose PowerShell runs in Constrained Language Mode could not inflate it and exited 1, which the Home reads as an ordinary ocx failure. Guard only the inflate so it exits 126; Invoke-Expression stays outside the guard so the inflated script's own errors and exit codes pass through. The new local-only fan-out test spawned the CLI with a stripped POSIX environment (no SystemRoot, a ':' PATH, a sh-script ssh) and without the owned-service-home preload that keeps Codex write admission away from the runner's real Task Scheduler. It timed out on Windows and could not prove "no ssh" there at all. Give CLI children the inherited environment and the owned-service-home fixture, add a Bun-spawn ssh sentinel with positive controls, and prove local-only in process as well. * docs(structure): separate inflation refusal from contained-run failures in the Windows contract * docs(devlog): record the measured encoded-size cost of the inflate guard * docs(devlog): quote the shipped wrapper and control in the plan * test(link): pin the fan-out Home outcome and keep inflated script errors out of 126 Address review: the local-only subprocess case now asserts the Home outcome its fixture is built for, and a Windows case shows an error raised by an inflated script keeps its own exit instead of the containment refusal (packedPowerShellArgv is exported for it). * ci(test): record durations for the #8099/#8100/#8103/#8104 test files and shorten the pinned-sync watchdog case PR test 4/4 at 7f4924e hit the 15-minute job timeout (job 125416446261) with no failing test. Its merge with dev ran tests/update/update-orchestrated-entry.test.ts (#8103) for 98 s, and like the other files #8099, #8100, #8103 and #8104 added it had no recorded duration and weighed the 176 ms median: the shard predicted 728 s and ran 852 s of tests. Both tables are refreshed with scripts/ci/test-durations.ts. Linux/macOS: run 41462914814 (dev 09b5585, Linux and macOS shards) plus the Linux shards of pull_request runs 41485932590, 41482553737 (#8103) and 41483744349 (#8104); every test file now has a row and the four Linux shards predict 716 s each. Windows: run 41462914814's eleven shards; the #8103/#8104 files and three files of the two stopped shards have no Windows measurement yet and still weigh the median. The POSIX "version writer ignoring signals" case waited out the production ten-second watchdog. It now runs the same script with a one-second watchdog and asserts the shipped script keeps "sleep 10": the case drops from about 10.4 s to 1.4 s.
* fix(ci): make the Windows batch shards green after #8103 Run 41491122166 left four Windows shards red; dev was green on all eleven in 41262132337 before #8103. - update-orchestrated-identity: the fixture used POSIX paths that are not native on win32. The product also rejected a Windows source path written with '/' separators, so a live install could read as unknown; win32 now equates only the separators (dotted, doubled and wrong-suffix forms still fail closed). - update-bun-ownership-lease: the fixture did not recognize the Windows installer bridge (runWindowsInstallerSync) that #8103 introduced, so it answered unconfirmed and the updater kept its fence. - claude-intercept-acl-barrier: stub the PowerShell owner-ACL probe like the writer-barrier suite, so a slow cold probe cannot use up the 1 s ACL budget before the mocked icacls runs. - update-mutation-guard (windows 9/11): this lint runs only on Windows, where Bun can start the TypeScript sync client, so #8103 landed with 1258 unreviewed findings. Review every #8103 site into the existing tables plus two orchestrated-module sidecars; accept exit 10 only for the exact enterMutation refusal branch, with mutants; orchestrated-entry launches its child through probeSpawn, as job.ts does. - Windows durations refreshed from runs 41491122166 and 41462914814. - storage-policy and lab-read-surfaces left unfinalized prepared statements that keep SQLite files open, so each Windows afterEach removal waited its 15 s retry budget (about 210 s per run) and leaked the home. Plan and evidence: devlog/_plan/261103_windows_batch_shards/010_plan.md * test(update): reject relaxed mutation-entry refusal shapes Add mutants for an unawaited or different initializer, an inverted entered.ok predicate and an else branch (CodeRabbit review). * test(update): pin the deferred-import admission fixture to runUpdateWithSession #8103 moved the three reviewed guardedStartWindowsTray deferred imports from runUpdate into runUpdateWithSession, and the policy table follows the code. The fixture now uses that context; the fourth binding, raw namespace, unreviewed binding and other-context cases stay rejected. Windows-only lint; reproduced and verified under Node. * fix(update): hand the orchestrated child a parent-issued result channel The orchestrated update child now writes its outcome records only into a result file its parent created for it. The parent creates a fresh temp directory holding one exclusively created capability file and passes the token in OCX_UPDATE_RESULT_CAPABILITY. Before any session write, mutex use or identity probe, the child accepts only a canonical, single-link, non-symlink regular file (owner-only on POSIX) whose size and entire content equal that capability document, then publishes every record through the descriptor it validated (truncate, then write at explicit offsets). The parent reads, unlinks and non-recursively removes only the file and directory identities it recorded, and bounds the record size. Update-lint rows are re-reviewed for the new sites, and the POSIX supervisor and marker rows now state only what the code enforces. Tests cover rejected destinations left byte-identical, a path swap after validation, interrupted and oversized records classifying as uncertain, identity-bound cleanup, and real Bun and Node parent-to-child handoffs under a symlinked temp root. * fix(update): compare result-channel file identity with BigInt stats NTFS and ReFS file ids can exceed Number.MAX_SAFE_INTEGER; every identity-bearing lstat/fstat in the result channel and destination validation now uses { bigint: true } (CodeRabbit review). * fix(update): scope the result handoff token and channel cleanup Remove the result capability from the parsed environment so probes and installers never inherit it; the Node launcher passes it only to the Bun process continuing the same child. Channel cleanup removes by path only on POSIX with non-replaceable ancestors, and otherwise leaves the channel. Cover symlinked and junction destinations in both Node and Bun. * test(update): qualify result channel and marker review rows State that cleanup identity checks precede path-based removal on POSIX only, and that marker publication assumes the OpenCodex home cannot be replaced.
* Normalize Chat Completions client error bodies * Cover remaining Chat error writer paths * Keep flat native error frames on the relay path * Bound flat error frame metadata * Keep reasoning summaries in flat frame projection * Rebuild named error frames before early relay branches * Document proxy-written Chat Completions error messages * Preserve proxy-authored Chat error responses * Keep undeclared tool refusals free of tool names * Bound proxy error destination labels * Bound model labels and proxy error details * Update the combo refusal example in the combos guide * Use client model labels for combo terminals and logs
…8011) * fix(security): reject unsafe inherited Windows grants * fix(windows): preserve existing ancestor ACL compatibility
…licy fallback (#8013) * fix(responses): preserve quota provenance across wrappers * docs(responses): bind quota provenance through lifecycle wrappers * fix(combos): reject reflected free-tier quota evidence on validation errors
* fix(advisor): honor web search tool choice * docs(advisor): explain manual tool-choice scope * fix(advisor): refuse disallowed search-loop calls before delivery * fix(advisor): revoke synthetic permission during empty recovery * test(advisor): cover reachable search allowlist limit
* fix(responses): scope tool authority to current turn * fix(responses): preserve deferred tools and current-turn identity Separate current-turn normalization from explicit-empty and replay enforcement. Keep tool-choice filtering and canonical namespace provenance at delivery. Read direct decoded tool names without bridging signed history a second time. Restore the 70 existing regressions and add direct-budget and namespace continuation coverage while retaining historical-tool refusal. * test(responses): avoid overlapping dev registration insertions Place the two current-turn test registrations after the existing entries. This preserves the same mapping while allowing dev's independent graceful output registrations to integrate without a textual conflict. The explicit map and independent golden previews retain all four names exactly once.
* fix: bound canonical patch prefix scanning * test(responses): cover fragmented long patches through routed streams * perf(responses): bound decoded patch preview suffix checks * test(responses): assert the decoded patch suffix bound
…8014) * fix(codex): admit automatic v1 reconciliation before CLI selection changes * test(copilot): drain case-owned work before restoring fixtures * test(copilot): preserve teardown failures and restore independent state
…8112) * fix(codex): read Artificial Analysis free model list before sunset - Point role capability at /api/v2/language/models/free; the legacy /api/v2/data/llms/models sends Sunset: Wed, 04 Nov 2026 23:59:59 GMT and answers 410 Gone afterwards, which would drop every keyed user to price tiering - Follow pagination.has_more page by page, capped at 10 pages per daily refresh and 16 MiB across all pages; a longer or larger list fails the read so price ranking takes over - Score every domain by the Intelligence Index, since the Coding and Agentic Indexes are always null and the free list has no math index - Keep the key on the fixed HTTPS host with redirect: "error" and the 30 s timeout per request - Add tests for two pages then stop, the page cap, the cross-page byte bound and the legacy path answering 410, and move the existing tests to the new URL and field - Update the auto-assign guide, the configuration reference and structure/subagents.md * fix(codex): fail an Artificial Analysis page without pagination - Treat a page missing pagination.has_more as a failed read so price tiering takes over, instead of ending the list early - Give the single-page fixtures the pagination block the API requires * test(codex): cover a present non-boolean pagination.has_more - Check that a string or null has_more fails the read the same way a missing one does
…8123) The current-turn catalog read nameless hosted declarations as having no wire name, so delivery dropped the tool_search function that translated adapters expose. Add that function name only when the current turn declares the hosted tool; replayed declarations still grant nothing.
* fix(responses): route local compaction checkpoints (#8089) * test(layout): keep the layout map under the size threshold
…imi Code Responses (#8122) * fix(kimi): drop the unsupported output.encrypted_content include on Kimi Code Responses (#8075) * fix(kimi): scope the include strip to the HTTPS default-port endpoint and document it * test(kimi): cover duplicate output.encrypted_content entries * docs(kimi): note the Kimi Code include normalization in the adapters reference
* fix(server): account for normalized inbound JSON * fix(server): measure parsed JSON size without recursion Normalized JSON byte accounting now walks the parsed value iteratively and returns the UTF-8 length JSON.stringify would produce, so deeply nested valid bodies are measured instead of throwing. * fix(server): keep JSON size measurement memory bounded by depth Walk containers with one cursor frame each instead of queuing every element, so wide arrays add no allocation proportional to their length. * docs(server): describe JSON measurement memory precisely * ci: run the full Ubuntu shards for this head --------- Co-authored-by: JUN <jun@lidgeai.com>
* fix(security): require paired session for CLI updates * test(security): exercise paired CLI update admission Redeem a real local pairing grant before exercising update admission and retain confirmation, ingress, origin, CSRF, expiry, and current-session guards. Document supported pairing origins and the independent local CLI path across the public guide, generated capabilities, and owning structure references. * test(security): pair published management apply fixture Obtain and redeem a real CLI pairing grant before exercising lost-response update admission. Assert that ordinary sessions are refused without creating the durable ledger, while retaining all existing retry and status checks. * perf(responses): avoid hashing bounded native output identities * test(responses): pin native identity UTF-8 byte boundary (cherry picked from commit 03c1075) --------- Co-authored-by: JUN <jun@lidgeai.com>
) * fix(kimi): export declared effort ladder and default to Kimi Code (#8095) * fix(kimi): skip effort export when reasoning is explicitly unsupported * docs(structure): record the Kimi Code effort export contract * test(clients): expect image_in among Kimi capabilities now that effort ladders add thinking * fix(kimi): ignore non-string effort metadata; document the Kimi Code effort export
* fix(antigravity): stop sibling discovery after revocation * test(antigravity): revoke discovery authority before bearer send * fix(antigravity): recheck discovery authority after refresh lock * fix(oauth): recover independent callers after discovery cancellation * refactor(oauth): move OAuth error classes into oauth-errors module Pure move to keep src/oauth/index.ts under the file-size ratchet; index re-exports the public classes. * fix(antigravity): stop sibling discovery when the active account loses authority Sibling discovery now rechecks the active account's needsReauth, pause, expiry and credential generation before each sibling refresh and send, not only the model-cache generation. * fix(oauth): recheck needsReauth under the refresh lock for usable-account callers A caller that requires a usable account checked needsReauth only before waiting for the per-account refresh lock. A sibling marked needsReauth while waiting could still send its stored refresh token, and a successful refresh then cleared the mark and returned a bearer for model discovery. The generic and xAI lock paths now recheck needsReauth for such callers, the resolver rechecks before adopting a refresh result, and an unrestricted caller that joined a usable-only refresh retries on its own. * fix(oauth): keep a needsReauth mark set during an in-flight refresh --------- Co-authored-by: JUN <jun@lidgeai.com>
#8128) * fix(claude): answer uncoded context-window refusals as prompt too long (#8127) * fix(claude): require definite overflow wording and skip rate-limit or credential statuses * docs(structure): record the translated-lane overflow classification * fix(claude): require a stated request size and ignore quoted values in overflow wording * fix(claude): compare stated counts, reject negated verdicts and strip typographic quotes in overflow wording * test(claude): drop trailing blank line * fix(claude): tighten uncoded overflow wording per review (exceeding, negation scope, conditional, non-string message)
* fix(cli): attest Codex listener before token injection * fix(cli): bind listener proof to the Codex destination Carry the actual challenged origin through credential admission and compare it with the final standalone or direct-client destination before loading a token. Preserve existing launch guards and cover port, address-family and proof snapshot behavior with the real attestation verifier and synthetic responses. * fix(cli): keep direct-client launches on their own listener proof A direct client's ownership proof comes from its machine listener, while Codex dials the hub URL, so comparing the two always withheld the client credential. Bind the proof origin to the destination only for standalone installations; direct clients still require a proven local runtime and keep their connection-state and loopback checks. * fix(cli): require the client listener role for direct-client proofs A direct client's credential could be authorized by any listener of the same home that answered the ownership challenge. Carry the proven listener's client role into the proof and refuse direct-client injection without it. * Revert the client-role requirement for direct-client proofs The role a listener reports is not covered by the ownership proof, so requiring it added no authenticated guarantee. Binding the role would need a change to the attestation format shared with the desktop app. Direct clients keep the home-ownership proof, which dev does not require at all. * docs(cli): state what the direct-client listener proof covers * fix(cli): prove a localhost listener at the address it binds A configured localhost binds 127.0.0.1, which is also what the standalone Codex target dials; probing the raw name produced a different origin and withheld the automatic token. --------- Co-authored-by: JUN <jun@lidgeai.com>
* fix(kimi): report monthly Coding Plan quota * fix(kimi): validate monthly ratios and continue envelope fallback * fix(kimi): keep nested legacy quota windows and validate counter ranges * fix(kimi): treat a quota counter overage as exhausted A used count above the limit, or a negative remaining count, means the window is used up; dropping the row would let an over-quota account look available. Impossible counters still fall back to the ratio. * fix(kimi): let either counter overage exhaust the quota window A negative remaining next to a lower used count still means the window is used up; check both overages before choosing a counter. * fix(kimi): check quota exhaustion before rejecting counters An exhaustion signal now wins even beside an impossible companion counter, and a positive remainder can no longer round up to an exhausted window. --------- Co-authored-by: JUN <jun@lidgeai.com>
* fix(codex): keep profile keys OpenCodex does not manage Reinjection rebuilt ~/.codex/opencodex.config.toml from the template, so an operator's own Codex root keys (stream_idle_timeout_ms, stream_max_retries, ...) vanished on the next start without a message. Carry root keys this build does not own into the rebuilt profile, ahead of the first table. Values that span several lines are copied whole, keys owned by either profile shape (model_provider, model_catalog_json, openai_base_url) are always rewritten, and a profile with nothing to carry keeps the generated bytes byte for byte. * fix(codex): carry unmanaged profile keys as source text and keep generated tables authoritative Carried root assignments keep their original bytes, so an integer outside the JavaScript safe range in an array or inline table is no longer dropped. A carried key named like a table the generated profile writes (features, model_providers) is dropped instead of redefining that table. The injector re-reads opencodex.config.toml under the write lock and refuses retryably when it changed after the plan read it. * docs(codex): describe which profile keys survive regeneration * fix(codex): keep a carried assignment's trailing spaces * test(codex): keep profile preservation tests inside their own Codex home Install an isolated CODEX_HOME before loading the path-dependent modules and refuse to run when the profile path is outside a test home, so a run without the repository preload cannot remove a real profile. * fix(codex): close multiline strings that end with extra quotes TOML lets a multiline string close with up to two content quotes before its delimiter. The structural scanner treated those as a new string and swallowed the following root keys, so profile regeneration dropped them. * fix(codex): roll back the profile only when this injection published it A refusal before the profile publish, including the stale-profile check, restored the captured pre-image over an edit that landed after the capture. Rollback now reverts the profile only when this operation published it and the file still holds those bytes; config and journal rollback are unchanged. * fix(codex): treat a profile removed before the plan reads it as absent The plan checked existsSync and then read the profile, so a removal in between aborted the run with a raw ENOENT. It now reads directly, treats ENOENT as absent and rethrows other errors; the lock-time comparison still refuses retryably if the profile reappears. * fix(codex): read the profile at lock time without an existence check The lock-time comparison now shares the plan's reader, so a profile removed during the read is treated as absent and the change is refused retryably instead of escaping as ENOENT. --------- Co-authored-by: T <t@x.io> Co-authored-by: JUN <jun@lidgeai.com>
* fix(kiro): report content-filter stops as non-retryable refusals A tool-less turn that Kiro's safety filter refuses (CONTENT_FILTERED, e.g. the Claude BIO classifier) arrives with no content. Explicit stop reasons were only mapped in required completion mode, so a disabled-mode refusal fell through to the retryable empty-stream error and clients received a 502 overloaded_error for a verdict that repeats deterministically. CONTENT_FILTERED and GUARDRAIL_INTERVENED now map to the non-retryable content_filter incomplete in every mode, which Messages renders as stop_reason refusal and Responses as a terminal failure. Kiro's refusal category and explanation are carried in the message. * fix(kiro): anchor content-filter checks ahead of the completion branches Same behavior: a validated final answer and a real tool call keep their precedence over a content-filter stop, now through an explicit guard. Placing both checks before the completion-answer branches keeps them first in order for every outcome that follows, and stops them sharing an insertion point with other changes to the same terminal sequence. * fix(kiro): keep first-attempt progress when the retry is filtered Review follow-up. A bounded retry that streamed text before CONTENT_FILTERED made the prior-attempt drain treat that text as superseding the first attempt's held progress, so the client lost it although neither attempt produced an accepted answer. The refusal check now runs before that drain and releases the first attempt's progress, then the retry's partial text, then the refusal. Server tests now cover partial text before a refusal on tool-less Messages (buffered and streaming) and Responses turns. * test(kiro): cover the buffered Responses refusal and document it Review follow-up. The tool-less refusal cases now include non-streaming /v1/responses, asserting the shared buffered contract (incomplete body, content_filter, no error) with and without partial text; both variants fail against dev's Kiro code. The providers guide describes the non-retryable refusal and what a filtered bounded retry shows. * docs(kiro): document the functions the content-filter change touches CodeRabbit's docstring-coverage pre-merge check (80% of touched functions with branches) reported 60%. Adds doc comments to the touched stream and event-parsing functions and to the test helpers; no behavior change. --------- Co-authored-by: JUN <jun@lidgeai.com>
* fix(command-code): reserve only live buffer growth * test(command-code): cover shared buffer ownership and drain --------- Co-authored-by: JUN <jun@lidgeai.com>
* fix(link): restore local routing when child stops * fix(client): restore owned Link routing before listener release * test(cli): align stop contracts with owned routing cleanup * test(client): preserve failure exit in signal cleanup contract * docs(link): document pending disconnect stop refusal Add an incomplete disconnect receipt (client_disconnect_pending) to the existing stop restoration refusal conditions in all eight lifecycle guides. Keep the recovery-evidence guidance and the runtime behavior unchanged. * fix(link): preserve handled-signal exit wiring * fix(link): allow catalog sync before signal restoration * fix(link): fence sync commits during signal shutdown * fix(link): keep runtime publication through its record builder * fix(link): retry transient stop-fence contention explicitly * docs(link): define the configuration mutation lock on first use * docs(link): keep operator recovery steps in the CLI lifecycle guide The structure note now states only the runtime behavior; starting and resyncing a Link Child after a signal stop is documented with the other stop steps in the public lifecycle reference. * test(link): use the canonical Codex home in sync-stop fixtures Sync records the canonical catalog path while the fixture created its native backup under the /var alias on macOS, so restoration fell back to an empty catalog; clean up held children with SIGKILL so a failed assertion is reported instead of hanging. Also correct the offline-sync wording: an offline sync does reapply routing. --------- Co-authored-by: JUN <jun@lidgeai.com>
* perf(devin): batch consumed stream chunk removal * perf(devin): compact stream chunks without a discarded array --------- Co-authored-by: JUN <jun@lidgeai.com>
#8009) * fix(codex): hold selection ownership through dashboard feature toggles * test(codex): cover feature invocation without injected toggle --------- Co-authored-by: JUN <jun@lidgeai.com>
…l turn (#8064) ZCode sends assistant text as the Responses message shorthand ({role, content} with no type). isAssistantTurnItem only recognized a typed message, so the shorthand closed the assistant turn and backfillBareToolCallTurnReasoning inserted its placeholder after the text instead of at the head of the turn. DeepSeek's thinking mode rejects that ordering with "The reasoning_text in the thinking mode must be passed back to the API", and every continuation of the session fails the same way. Normalize the item type the way the Responses parser already does, so a shorthand with role=assistant belongs to the surrounding assistant turn. The placeholder then precedes the whole turn, matching the typed-message shape that already passes. Co-authored-by: fec333 <7840187+fec123@users.noreply.github.com> Co-authored-by: JUN <jun@lidgeai.com>
) * fix(responses): unwrap one extra executable freeform body wrapper * fix(responses): stream object-leading executable bodies once they cannot be a nested wrapper * perf(responses): resume progressive freeform decoding across deltas Each delta decoded the whole accumulated canonical value and walked the whole decoded body again for a nested executable wrapper, so one-character deltas did quadratic work. A per-call ProgressiveFreeformInputReader now resumes where the previous delta stopped, and the nested-wrapper check is an incremental state machine that also releases a non-string nested input once no later string duplicate can follow. --------- Co-authored-by: JUN <jun@lidgeai.com>
* fix(kiro): resubmit a finished answer on the completion retry On a trivial request claude-sonnet-5 answers in plain text with END_TURN instead of calling the private completion tool, so the bounded retry fires. Its instruction began "Continue the existing task", which the model read as the user's next message: it called the completion tool with "there is no active task to continue", and that replaced the correct answer. The retry instruction now states that ordinary text has not reached the user and asks for an already complete reply to be resubmitted unchanged. The empty-completion classification is unchanged. * test(kiro): register the retry-wording test on an uncontended layout line Other open Kiro changes register their tests on the kiro-single-final line; moving this entry to the kiro-images line keeps the two registrations from conflicting when both land. * fix(kiro): keep a direct completion route in the retry instruction Review follow-up. The reworded retry instruction offered only a resubmitted reply, a blocking question, or another tool call. On a reasoning-only retry there is no previous reply, so a request the model can answer directly had no completion route in the message itself. It now says: if the task is complete, call the completion tool with the final answer, submitting the previous reply unchanged when that already was the answer. Documented in the adapter reference. * test(kiro): document the retry-wording test helpers CodeRabbit's docstring-coverage pre-merge check counts functions this PR touches; the new test file's helpers had no doc comments. --------- Co-authored-by: JUN <jun@lidgeai.com>
* Fix non-ASCII Codex turn metadata headers * docs: document ASCII-safe JSON contract for rewritten turn metadata * fix(codex): escape DEL in turn metadata header * docs(structure): record turn metadata header escaping * fix(codex): bound turn metadata by canonical size so re-isolation is idempotent * fix(codex): canonicalize matching turn metadata --------- Co-authored-by: JUN <jun@lidgeai.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Important Review skippedToo many files! This PR contains 631 files, which is 331 over the limit of 300. To get a review, reduce the PR to 300 files or fewer by splitting it into smaller PRs or changing its base branch. Usage-priced reviews support at most 300 files.
|
|
✅ Deterministic PR hygiene checks passed. |
⏳ DRAFT
What to do
Its title has been prefixed with |
|
Maintainer promotion of
|
Summary
Promotes the frozen
devcandidate7b3bb7a25768ecce7507fdb77ee0a5084f53b74ctomainas stable 2.97.0. The promotion heada18953be39merges inmainancestry (684ad75d2, v2.96.0) with theoursstrategy, so its tree is exactly the candidate tree (git diff 7b3bb7a257 a18953be39is empty). Candidate version sources already say 2.97.0;devmoves to 2.98.0 through thedev-version-bump.ymlpull request, which merges before publication.The candidate carries 43 commits since 2.96.0. Highlights:
ocx updateplan/apply (feat(update): orchestrated ocx update plan/apply contract (update.orchestrated/1) #8103), Homeocx syncfan-out to linked Children (feat(sync): Home ocx sync fans out to linked Children (sync.home-fanout/1) #8100).x-codex-turn-metadata(Fix non-ASCII Codex turn metadata headers #8040); unmanaged Codex profile keys kept (fix(codex): keep profile keys OpenCodex does not manage #8074); local compaction checkpoints honorcompactionRouting(fix(responses): route local compaction checkpoints #8119); uncoded context-window refusals answer "prompt is too long" on the translated Claude lane (fix(claude): answer uncoded context-window refusals as prompt too long #8128); Kimi Codeoutput.encrypted_contentinclude dropped (fix(kimi): drop the unsupported output.encrypted_content include on Kimi Code Responses #8122) and effort ladder exported (fix(kimi): export declared effort ladder and default to Kimi Code #8126); Ollamaprompt_eval_cached_countreported as cache-read (fix(ollama): report prompt_eval_cached_count as cache-read input #8124); normalized inbound JSON accounted (fix(server): account for normalized inbound JSON #8026); Kiro content-filter refusals non-retryable (fix(kiro): report content-filter stops as non-retryable refusals #8072) and completion retry resubmits the finished answer (fix(kiro): resubmit a finished answer on the completion retry #8070); Command Code NDJSON buffers no longer double-charged (fix(command-code): avoid double charging live NDJSON buffers #8047); Devin stream chunk removal batched (perf(devin): batch consumed stream chunk removal #8049); freeform wrapper unwrap (fix(responses): unwrap one extra executable freeform body wrapper #8048); untyped assistant messages kept in their tool-call turn (fix(responses): keep untyped assistant messages inside their tool-call turn #8064); bounded patch prefix scanning (fix: bound canonical patch prefix scanning #8012); link integrations restored before native signal exit (fix(link): restore owned integrations before native signal exit #8036); Artificial Analysis free model list endpoint (fix(codex): read Artificial Analysis free model list before sunset #8112).Verification
ci,enforce-target,hygiene, test 1/4–4/4) and a maintainer-integration record on the PR; security-boundary PRs carry a dedicated security review.dev(665cc48): 827 pass, 17 skip, 3 fail — the 3 are the published-launcher apply cases refused withblocked_active_sessionby live Codex sessions on the integrating host; they pass in CI shard 3/4.bun scripts/release-version-sources.ts check 2.97.0on the candidate: all 4 version sources carry 2.97.0.v2.97.0has no tag and no npm publication (npmlatestis 2.96.0).release.yml; that run is the candidate's cross-platform evidence (no separatelane=alldispatch was run for this window).Checklist