[WRONG BRANCH] release: sync dev into preview for 2.27.0 - #2161
Conversation
…te pinning Phase B2 of the FastWire umbrella (#1886), and a documented correction to what that issue proposed. The umbrella specified an atomic route pin for OpenRouter — only inject a tier alongside `provider: { only: [...], allow_fallbacks: false }` — to stop a tier reaching an upstream that would silently bill for it. OpenRouter's own documentation retires that requirement, and shows the proposal would not even have worked: - Tier endpoints are separate suffixed slugs (`openai/priority`), and they are explicitly NOT matched by base slugs. Pinning `only: ["openai"]` would have excluded the very endpoint that serves priority. - Priority tries tier endpoints first and falls back otherwise, and billing always follows the endpoint actually used — so the silent-overbilling risk the pin existed to prevent does not exist. - The response reports the tier actually served. Pinning would therefore have turned a graceful capacity fallback into a hard failure while protecting against nothing. Downgrade safety instead rests on B0's confirmation model, which was built for exactly this contract. What this adds: - The three OpenAI-backed slugs we ship get exact-model capability. The provider stays unclassified, and `anthropic/claude-sonnet-5` is left out because OpenRouter does not list Anthropic among its priority upstreams. - Registry model capability is now guarded by destination. A provider merely named `openrouter` but pointed at someone's own gateway must not inherit evidence gathered about openrouter.ai, and OpenRouter's endpoint is fixed, so the guard reads the operator's configured base URL rather than the routed one. Catalog and runtime both feed it that same configured value, keeping A1's one-resolver invariant intact. - The Chat surface finally reads the upstream's `service_tier` echo, closing the gap B0 left open. Without it every OpenRouter Fast request would have recorded `assumed` even when OpenRouter told us it had fallen back to standard. - A confirmed priority result with no bundled tier price is now billed at the standard rate but flagged a floor rather than silently reported as exact: OpenRouter documents priority as "faster, higher cost", so standard is provably a lower bound. Scoped to canonical priority only — flex is cheaper, so the same argument would be false there. A first attempt scoped capability with the registry's `preserveCustomDestination` flag. It worked, but that flag also decides provider claiming and hosted-tool preference validation, and the full suite caught it changing which configs `openrouter` accepts. The destination guard above replaces it and touches nothing outside FastWire. Full suite: 13361 pass / 10 skip / 1 fail — the pre-existing dev-side key-login-live-update regression, which reproduces on pristine dev. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Screenshot evidence for #2080: the Logs table showing a confirmed-priority OpenRouter request rendered as a floor (≥$) next to a response-declined downgrade and a standard request (~$). Taking this screenshot is what surfaced two defects the test suites missed: the lower-bound marker disagreed with the parallel xAI unit's rendering, and the fix for it initially reached only the detail panel because the table cell had its own inline formatter. Both paths now share one implementation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
resolveInputCeiling read modelContextWindows and modelMaxInputTokens with
a bare lookup, while the catalog resolves the same two maps through
modelRecordValue, which also accepts a family entry for a tagged id.
With contextWindow 8_000 and modelContextWindows {"gpt-oss": 131_072}:
catalog advertises 131_072 provider-fetch.ts:612
admission ceiling 8_000 before this change
So the gate refused turns the model can plainly hold, using a window that
belongs to a different model. That is the opposite of what this module
documents about itself -- "every uncertainty resolves toward admitting".
modelMaxInputTokens had the mirror of it: a family cap never applied to
the tagged sibling it was written for.
Three tests, all red without the src change and green with it. The first
asserts the catalog's value first so the two can never drift apart again.
No behavior changes for ids that already resolved exactly.
`ocx models` classified each row with bare lookups while the proxy
resolves the same four fields through modelInList / modelRecordValue,
which accept a family entry for a tagged id.
With models ["gpt-oss:120b"], noVisionModels ["gpt-oss"],
modelContextWindows {"gpt-oss": 131072} and
modelReasoningEfforts {"gpt-oss": ["low","high"]}:
runtime isModelTextOnly = true, window 131072, efforts [low, high]
ocx models {"contextWindow":null,"inputModalities":null,
"reasoningEfforts":null}
Every field came back unclassified, so a text-only model reads as
image-capable and a configured window reads as unset -- for a config the
proxy honours in full.
Two tests. The first asserts isModelTextOnly first, so the command is
pinned to the runtime's answer rather than to a copy of it; it is red
without the src change. The second pins exact-over-family precedence and
passes either way -- it guards the fix from over-reaching, it is not
evidence of the bug.
237 tests green across cli-models, vision-eligibility, codex-catalog and
input-admission. tsc --noEmit clean.
@Wibias stepped down from developing opencodex, and repository permission was reduced to read access. Move him out of the current-maintainers table into a new Former maintainers section, drop him from the CODEOWNERS default-reviewer line and the four high-impact runtime paths, and record the change with the 2026-07-27 addition entry it closes. Nothing he authored is unwound: commits, merged pull requests, release-note attributions, and the code comments citing his reviews stay as they are.
…stwire-b2-openrouter
isModelTextOnly returns true on the noVisionModels match before it ever reads modelInputModalities, so a `gpt-oss` noVision entry beats an exact `gpt-oss:120b` entry that lists "image". Resolving the exact entry first made `ocx models` advertise image support the proxy then rejects — the same class of drift this PR set out to remove. Add the conflicting-config regression case, which asserts the runtime's answer via isModelTextOnly before comparing the CLI's. Thanks @coderabbitai for catching it.
…cates An early break abandons the enumeration generator instead of resuming it, so the finally that closes the directory handle never runs. The periodic reclaim truncates by design -- entry cap, cleanup cap, wall-clock deadline -- which turned that into one leaked handle per truncated tick. Route every early exit through a stopScan() helper that calls iterator.return() before returning, and add a regression that fails when the fix is reverted. Also repairs the deadline test's oracle. Its fake clock started at 0 while the fixtures carried real epoch mtimes, making every computed age negative, so the files survived the 15-minute grace whether or not a deadline check existed -- the test passed against its own ablation. Anchor the clock to real time and add an explicit unbounded-run assertion so the deadline is the only reason nothing is removed.
…ly fire The budget warning keyed on eligible > removed + failed, which is unreachable outside a dry run: an entry is counted eligible and then unlinked or failed on the same iteration, so the two are always equal. An operator whose backlog exceeded the cleanup budget was told the reclaim had finished. Carry an explicit truncated flag on the scan result instead, set wherever the loop stops on a budget rather than on the end of the directory, and OR it across the swept directories. The dry-run report is bounded by the entry cap too, so a truncated report now says the count is a floor. The partial-reclaim test asserted a state production cannot reach; it now uses a reachable one and is paired with an ablation guard that fails if the warning stops depending on the flag.
A-reduced fails on Windows with exitCode 1 and the assertion said only "expected 0, got 1". runCli already captures stdout and stderr; the message now carries them, so the next Windows round names the CLI failure instead of leaving it to be guessed at. Diagnostic only.
The last four Windows composed-acceptance failures all resolve to one cause, which only became visible after the previous commit put the CLI's own output in the assertion message: CodexUserIdentityRefusal: Windows effective-account lookup timed out 8s is a generous ceiling for powershell.exe -Command on a real desktop and is not one on a GitHub Windows runner executing a quarter of this suite. The child was still starting, not hung — and bounding a hung child is the only thing that budget exists to do. Gated on CI alone, so a user's machine keeps the 8s ceiling exactly as before and the recoverable-refusal contract is unchanged where it matters. This is NOT a regression from this release range: src/codex/user-identity.ts has zero commits in main..dev. It is a pre-existing CI-only limit that was invisible until the diagnostics landed. The contract test now pins BOTH values rather than loosening to a range. Its comment says the point is to stop a silent re-tune, and a range would permit exactly that; two exact assertions keep the guard while admitting the second number. Ablated to confirm it fails without the change. Refs #2108
The previous commit widened the Windows identity-lookup budget on CI, and the Windows shard still refused with "effective-account lookup timed out". The reason is in this fixture: env() is a deliberate whitelist, so CI never reached the child and the CLI kept the 8s desktop ceiling. Named explicitly rather than inheriting process.env, which is what the whitelist is for.
The Windows shard caught a real hole in my own fix: "a symlinked database is still refused" went from pass to FAIL. The widening let through exactly what the guard exists to refuse. The bug was self-referential. databasePathIsSafe calls sameLogGuardPathIdentity(realpathSync.native(path), path) — so realPath is ALREADY the resolved form. Re-resolving the requested path produced the same value on both sides, and a symlinked database compared equal to itself. The check is now link-aware. A short-name expansion rewrites the spelling of components that are all still directories on one chain, so requiring that no component of the request is a link is sufficient: with none present, any remaining difference is the OS's own canonical spelling. A symlink or junction anywhere in the chain fails closed, and an unreadable component fails closed too. Refs #1729 Verification: 21 pass / 0 fail across the two Log Guard suites, tsc --noEmit exit 0. The symlink verdict was also checked directly against the exact call shape the caller uses — realpathSync.native(link) versus link — which is the shape that made the first version wrong and which the POSIX suites do not exercise. Worth recording plainly: this is the second time in this branch that a Windows fix of mine created a defect the platform legs then caught. The tests are doing their job; my first cut of a fail-closed boundary is not to be trusted without them.
The Windows shard showed a second failure mode behind the first: a case failing with "Expected: 0, Received: 143" on a child it had just spawned, immediately after Bun printed "killed 1 dangling process". That is a cascade, not six independent failures. A case that times out leaves a live `ocx start`. Teardown was supposed to reap it, but the wait threw on the first child that did not exit inside 10s, so the rest of the loop — including every remaining child and the lock-file cleanup — never ran. The survivor was then killed by Bun's between-file sweep, and the next case's child died with it. Two fixes, both in teardown: - cleanup() now SIGTERMs every child, waits for each independently rather than aborting the loop, and SIGKILLs whatever is still alive. A survivor is strictly worse than an ungraceful exit; the case is already over. - afterEach drains every fixture before reporting, so one fixture's teardown failure cannot strand another fixture's children. This does not make the underlying case faster. It stops one slow case from being charged to unrelated ones, which is what made the Windows failures look like a moving target across runs. Refs #2108 Verification: 8 pass / 0 fail locally, tsc --noEmit exit 0. The Windows shard is the only place the cascade reproduces. Context worth recording: WP13 has never passed on Windows. It has zero commits in main..dev, and the 2026-08-18 run I originally compared against had shard 4/4 CANCELLED, so those cases never executed there. "Pre-existing on dev" was true; "already known to pass" was not, and I stated the second when I only had evidence for the first.
…cale The Windows shard said "this test timed out after 30000ms" on two cases while the file's per-case budget is 150s on CI. Both were hardcoded 30_000 values the earlier commit missed: the E lock case's own per-test budget, and the restore watchdog inside the Restore-truth case. So those two were never given the headroom the rest of the file got, and their failures were being read as slow-runner evidence when they were a stale constant. Both now use the same scale as everything around them: CASE_TIMEOUT_MS for the per-case budget, watchdogMs() for the in-test watchdog. No new numbers. Refs #2108 Verification: 8 pass / 0 fail locally, tsc --noEmit exit 0, and a grep confirms no bare 30_000 remains in the file.
…al-path fix(log-guard): accept the OS's own canonical spelling on Windows
docs(devlog): record release readiness for 2.27.0 and the Windows gate decision
docs(devlog): record the local Windows verification behind the 2.27.0 promotion
# Conflicts: # package.json
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
✅ Deterministic PR hygiene checks passed. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (155)
📝 WalkthroughWalkthroughThis release updates Windows runtime handling, response-state recovery, service definitions, provider and tool routing, cost reporting, type-module organization, documentation, CI timeouts, and maintainer metadata. ChangesRuntime and platform behavior
Provider, tool, and protocol behavior
Documentation and repository operations
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
⏳ DRAFT
What to do
UI screenshot waived by a maintainer comment. |
|
Maintainer note: this is the release-train sync of dev into preview accompanying the 2.27.0 promotion (#2159), which is the documented exception to the dev-only target rule. Tree is byte-identical to origin/dev, so this does not change gui. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Summary
devintopreviewso the prerelease train carries the same code as the 2.27.0 stable promotion ([WRONG BRANCH] release: promote dev to main for 2.27.0 #2159).origin/dev(git diff origin/dev HEADis empty).package.jsonversion line, resolved to2.27.0. A preview release still requires a-preview.prerelease version, whichrelease.tsandrelease.ymlboth enforce on thepreviewbranch; this PR only syncs code and does not cut a preview release.Verification
Same head as the 2.27.0 promotion, verified locally on Windows 11 with Bun 1.3.14:
bun run typecheck- exit 0bun run test- 8028 pass, 1 pre-existing environment-dependent failure, plus the Bun panic tracked in [Bug][Windows]: six pre-existing CI shard failures (WP13 startup cost, symlink fixtures, Bun panic) #2152Checklist
Summary by CodeRabbit
New Features
ocx doctordiagnostics and optional cleanup for abandoned response-state temporary files.Bug Fixes
Documentation
Chores