[WRONG BRANCH] release: promote dev to main for v2.40.0 - #3261
Conversation
* docs(devlog): plan the v2.39.0 release train * docs(devlog): record the five-lane v2.39.0 regression audit * docs(devlog): close the v2.39.0 release train with the shipped outcome * docs(devlog): name the server-auth flake follow-up * docs(devlog): record the executed preview promotion * docs(devlog): record the executed main promotion * docs(devlog): record the executed publish
…vent that never fires (#3129)
The ordinary field-mask PATCH resolved the destination without the allowBenchmarkAddresses opt-in that POST and the re-enable path already use for the canonical built-in OpenAI forward provider. Under Clash/Mihomo fake-IP DNS (chatgpt.com → 198.18.0.0/15) the same canonical provider that was created successfully could never be patched: every context-overlay PATCH was rejected with a benchmark-address destination error. Pass the same exception on PATCH, computed the same way (name === "openai" && isCanonicalOpenAiForwardProvider(next)). The exception stays scoped to the exact canonical transport seed: loopback, RFC1918, metadata, and mixed dangerous DNS answers still fail closed, and non-canonical or OpenAI-like custom providers gain nothing. (cherry picked from commit f463e12) Co-authored-by: Flowershangfromthebranches <152056395+Flowershangfromthebranches@users.noreply.github.com>
…er paths (rebase of #3104) (#3134) * fix(service): give a Windows cold start room to bind, without loosening zero budget Reimplements #3039 (author @ntdatt812), whose diagnosis and production logic are both right. confirmServiceServing had a fixed 20s deadline and returned as soon as the clock passed it. A Windows cold start does NTFS ACL hardening and previous- session journal recovery before the listener exists, so #3009 recorded a service that bound a few seconds late and then stayed healthy -- reported as a terminal failure with exit 1. The caller's fallback is to start a second proxy against a port that is about to be taken, which is worse than waiting. Windows now gets 45s, every other platform keeps 20s, and the loop knocks once more after a short grace before calling it dead. Two changes to #3039 as submitted: - It relaxed `expect(probes).toBe(1)` to `toBeGreaterThanOrEqual(1)` in the zero-budget test. That assertion is what stops a future change from sleeping when the caller asked not to wait, and "at least one" passes against exactly the version it is meant to forbid. The `waited` guard already preserves the contract, so the original assertion is restored and the comment says why. - Its Windows-budget test asserted only `toBeGreaterThan(linux)`, which accepts 21s. The reported service bound past 20s, so the number is the contract: the test now pins 45_000 absolutely. Mutation-checked, both restored afterwards: remove the `waited` guard -> 181 pass / 1 fail, the zero-budget test remove the grace probe -> 181 pass / 1 fail, the #3009 test Closes #3009. * fix(service): forgive only what the code page mangled in a scheduler path Reimplements #3067 (author @ntdatt812). The diagnosis is right and the relocation is right: schtasks converts its XML through the console code page before the bytes exist, so runFile reading as a buffer cannot help. A profile named outside that page comes back as C:\Users\???\... and the exact comparison rejected a registration this process had just created correctly, so `ocx service install` rolled it back (#3064). The remedy needed narrowing. #3067 compiles every unrepresentable run to `[^\\/]*`, which forbids a path separator but allows arbitrary ASCII. A segment that is ENTIRELY non-ASCII then has no anchors left, so C:\Users\<CJK>\.opencodex\service-launcher.vbs matches C:\Users\Admin\.opencodex\service-launcher.vbs and this process would adopt, repair, or delete another account's task. The same hole applies to <UserId>, where MACHINE\<CJK> would match MACHINE\Admin. Its tests use "Người", whose surviving Ng and i letters hide the case. Here an unrepresentable run may match only a run of substitution characters -- '?' per character, U+FFFD, or nothing -- and every ASCII segment, including every separator, is matched literally. A foreign account's path fails because "admin" is not a run of substitutions. Mutation-checked: widening the class back to `[^\\/]*` gives 186 pass / 1 fail, exactly "rejects another account's path that is merely the same shape". Closes #3064. * fix(service): bind scheduler recovery to exact SID * fix(service): fail closed on ambiguous scheduler ownership * test(service): lock scheduler ownership guards * test(service): scope scheduler verification fixtures * test(service): exercise scheduler ownership oracles
…l mid-fixture (#3139) * docs(devlog): plan merge train round 3 Roadmap for landing the green PRs, retiring the superseded ones, and rebasing the rest, frozen at dev=132b557ad. Includes the round-1 audit synthesis: three blockers folded (fork PRs are carried by cherry-pick rather than force-pushed, because enforce-pr-target.yml applies the readiness checklist to authors without push permission; #3039's closure withdrawn because #3104 prints the configured budget where #3039 printed the elapsed wait; the src/service.ts overlap is 330470e, not 0ef04e6) and two rebutted with evidence. * docs(devlog): record wp1 — #3114 landed as abcda8e * docs(devlog): record the wp2 security review for #3122 * docs(devlog): record wp3 — #3134 landed, #3128 flake premise corrected * docs(devlog): record wp5 — #3077 closed, #3109/#3112 rebased * docs(devlog): locate the websocket refresh flake, and correct the #3128 premise * docs(devlog): prove the flake mechanism and correct its direction * docs(devlog): mark the superseded flake explanation in the wp5 record * test(auth): install the fake clock and fetch stub before startServer startServer returns synchronously but arms an async pool-quota prime that outlives its return (src/server/index.ts:2054-2064). That prime calls getValidCodexToken, which can rotate the very credential these assertions read, and fetches a real host unless the stub is up. Both fixtures installed Date.now and globalThis.fetch AFTER startServer, leaving a window two dynamic import() resolutions wide where the prime ran against the real clock and real fetch. On a warm local module cache it resolved before the fixture finished; on a loaded CI runner it did not, and seenAuth[0] was already the rotated token. Measured rather than assumed: OPENCODEX_DEBUG_QUOTA=1 prints refreshed=1 on every run of both the fixed and unfixed trees, so the prime always fires. The fix does not suppress it -- it makes it run inside the fixture's controlled world. The thread-affinity test at :2131 had the identical shape and is fixed too.
…soning_details contract (#3132) * fix(minimax): align split-reasoning wire schema with the official reasoning_details contract MiniMax M-series with reasoning_split returns thinking as a structured reasoning_details array whose stream deltas repeat each segment's full text-so-far, and the interleaved-thinking guide requires that array back verbatim on the next turn. The adapter dropped reasoning_details entirely and replayed a reasoning_content string, so streamed thinking never surfaced and tool-use continuations lost the reasoning chain. Add a reasoningDetailsModels registry knob (wired through derive/router like reasoningSplitModels), prefix-diff cumulative reasoning_details stream deltas with an incremental fallback, read the array as a non-stream fallback, and serialize preserved reasoning as a single reasoning.text segment for listed models. Both minimax and minimax-cn opt in. Evidence: platform.minimax.io/docs/guides/text-m3-function-call and /docs/api-reference/text-openai-api (verified 2026-09-01). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(minimax): gate reasoning_details parsing on the routed model A nonempty reasoningDetailsModels list was enabling MiniMax snapshot parsing for every openai-chat model on the provider. Carry the last requested model into both parser paths and match with modelInList. Also update the MiniMax #950 replay assertion: preserved reasoning now serializes as reasoning_details, which is what made shard 4/4 red. --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Nine sites across three suites stood in for an exited process with a
hardcoded pid:
const deadPid = process.pid === 4242 ? 4243 : 4242;
The code under test asks the kernel whether that owner is still alive, so
the pid is only dead until an unrelated process happens to hold it. Then
production answers correctly, the test reads that as a miss, and the
failure looks like a defect in the feature rather than in the fixture.
That is not hypothetical. On the macOS host where this was found, pid
4242 was `liveactivitiesd`, and five tests failed together on a clean
`dev` checkout: `periodic reclaim frees abandoned temps without any
continuation access`, both `doctor reclaim wiring (end to end)` cases,
and both `status reports stale process records end to end` cases. The
three files together went 186 pass / 5 fail before this change and 191
pass / 0 fail after it, with pid 4242 still held by `liveactivitiesd`
across both runs.
The probe already existed. `tests/responses-state.test.ts` did it inline
for one test, with a comment naming this exact hazard on a shared CI
runner, while four sites in the same file and four more in
`tests/cli-status-json.test.ts` and `tests/doctor.test.ts` kept the
assumption. This lifts that probe into `tests/helpers/dead-pid.ts` and
uses it at every site, so the knowledge lives in one place rather than in
a comment beside one of nine copies.
The helper throws rather than returning a sentinel: the inline version
needed `expect(deadPid).toBeGreaterThan(0)` at its call site, and a throw
gives every caller that guarantee without repeating the assertion. ESRCH
is the only accepted answer — a successful `kill(pid, 0)` means alive and
EPERM means alive but owned by somebody else.
Other `4242` literals in the suite are injected fixture data read through
mocked accessors, never probed against the kernel, and are left alone.
Verified on macOS: bun run typecheck clean, bun run privacy:scan passed,
bun run test 16514 pass / 0 fail across 998 files.
(cherry picked from commit d3c3e3a)
Co-authored-by: SEUNGWOO LEE <69357689+lifrary@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five landings, six closures, two rebases, and the flake that held the last PR. Records what the round is evidence of rather than only what it did: three explanations were written for the websocket flake and two were wrong, both plausible enough to justify the same fix -- caught by activation evidence, not review. The '#3128 fixed that flake' citation was repeated across three PRs and taught reviewers to dismiss a red that was real.
* test(auth): seed the pool quota and credential after the clock is pinned The websocket refresh test still failed on loaded CI runners after #3139, on both macOS and Linux, and dev's own HEAD fails it too — so it was not something any open branch introduced. Two writes stamp real time when they run before the clock is pinned: updateAccountQuota sets updatedAt: Date.now(), and saveCodexAccountCredential sets replacedAt. Everything after the pin reads the pinned 2027 value, so the gap is about 136 days against a 6-hour freshness window (QUOTA_DISK_MAX_AGE_MS, src/codex/quota.ts:491). The seeded state reads as stale no matter how fast the runner is, the startup pool-quota prime refreshes the credential before the first turn is served, and seenAuth[0] is already the new token — which is why the failure diff was always the first element. #3139 pinned the clock and the fetch stub before startServer, closing the window for the prime's own reads. It could not close a window for timestamps written before either was in place. Both seeds now run after the pin. Timing-dependent by nature: the mismatch does not reproduce locally either before or after, so the evidence is the mechanism rather than a local red-to-green. A 136-day gap against a 6-hour window is arithmetic, not a race. Twelve consecutive local runs are clean. * test(auth): restore the affinity test's quota seed after the pin The previous commit removed `updateAccountQuota("pool-a", 10, 5)` from the `expired thread affinity` test along with the websocket test's own seeds. That seed belongs to the affinity test, and its comment kept pointing at a call that was no longer there. Restore it on the correct side of the clock pin. Note what the comment now claims and what it does not: seeding after the pin is what keeps the startup pool-quota prime quiet, because `primeCodexPoolQuotas` treats a missing entry as stale exactly like an expired one (src/codex/auth-api.ts:1334). It is not a race fix for `expect(upstreamRequests).toBe(3)` — `redirectCanonicalCodexTo` only rewrites `/backend-api/codex`, while the prime's WHAM call goes to `/backend-api/wham/usage` and never reaches the counted upstream. Verified with `bun test tests/server-auth.test.ts`: 91 pass, 0 fail. --------- Co-authored-by: jun <jun@lidge.dev>
…t, add identity allowlist, name session consumers and /v1/catalog admission
… compat, per-machine usage requirement
… file, protocol compat floor, contradictions closed
…served the traffic; no mirroring
…80 (synthesis in 002)
…an .prev crash-window recovery
…ract Rebased onto current dev and repaired the contract defects the review raised on the previous head. D1 - remove insecure-http-pairing. A reusable pairing grant crossing non-loopback plaintext HTTP is captured verbatim by a passive observer, and the config opt-in gating it could not bound the risk it recorded. The "bootstrap over HTTP then upgrade" variant is rejected too: the plaintext hop has no trust anchor, so an on-path attacker substitutes its own valid HTTPS origin and the upgrade authenticates the attacker. remoteGui.allowInsecureHttp is deleted and a persisted true is dropped with a warning. D2 - /v1/catalog emits no ETag and never answers 304. The response varies by key type and key id, so a shared strong validator lets a store revalidate one identity's representation for another; private, no-cache does not prevent storage, and the revalidation is what crosses identities. /api/catalog keeps its validator because it is loopback-scoped and identity-invariant. D3 - forward the browser Origin verbatim on every allowed session-authenticated request, not only POST /opencodex-session. The session is origin-bound and mutations enforce Origin/CSRF, so relayed writes were losing the evidence the hub requires. Synthesizing an Origin is refused: the relay would attest to something it never observed and the hub would validate the relay against itself. D4 - compare candidate identities before probing during rotation recovery. A crash after pendingOperation is persisted but before the token is replaced leaves both files holding the old key, where both probe successfully; the old "both accepted implies commit" rule read that as a completed rotation and lost the new key permanently. Identical candidates now mean pre-replacement: never commit, resume instead. Unconfirmed abort or restore retains evidence rather than installing a guessed generation. D5 - relayed session, bootstrap, and management responses are rewritten to no-store with ETag and Last-Modified stripped. Preserving an upstream validator reintroduced D2 one layer up, at exactly the position where an intermediary cache is most likely to sit. Also corrects the 000_research framing of the remote gui-session limitation. It is a deliberate fail-closed restriction already visible in shipped code and documented publicly, not an unreported weakness, so calling it a defect invited the wrong reading of what belongs in a public devlog.
The previous commit closed D1 where the review pointed (040) but left the enabling contract alive in three other documents: 010 still listed insecure-http pairing as evidence rung 4, 050 still defined the client --allow-insecure-http option plus an allowInsecureHttp field on the exchange call, and 070 still named remoteGui.allowInsecureHttp as a Phase-2 key. A contract that removes a path in one document and specifies it in three others is not a fix; an implementer reading 050 would have built the option. 050's "both sides must opt in" rationale is also removed rather than reworded. Requiring two opt-ins makes the choice deliberate, but deliberateness is not the control that matters here: the grant is still readable by anything on the path and the session it mints is still reusable. The client now refuses before transmission instead of warning after it. 010 additionally records why the "don't over-harden" valve does not need this path: tailscale serve terminates HTTPS for exactly that deployment, so rung 3 already covers the private-tailnet sole-operator case the valve was for. P3-A3 is rewritten from "succeeds with explicit warning" to refusal in every combination, including a tree still carrying the legacy CLI argument or a persisted config key.
An adversarial review of dfae1da found four contract defects still live and one bookkeeping error. All are repaired here. D2 was fixed in Phase 1 but not in the phases that consume it. 030 still described ETag/If-None-Match as "shared" between the two catalog routes, and 050/080 still specified a client that persists an ETag, sends If-None-Match, and handles 304 — against a route that no longer emits a validator. An implementer following Phase 3 would have rebuilt exactly what Phase 1 deleted. The client now fetches unconditionally, and an unsolicited 304 is a protocol error rather than a cache hit, since the client never issued a conditional request. D3 overcorrected. Saying Origin must never be omitted contradicted the Phase-2 predicate, which deliberately allows Origin-absent safe GET/HEAD reads. The two rules are now separated: forwarding is verbatim whenever the browser sends a value, while requiring Origin stays the hub predicate's decision. The relay refuses only where it would otherwise have to invent a value. The owner test row now names each mutation method plus both Origin-absent branches, instead of testing only the pairing exchange. D4 stated the new identity-comparison rule while leaving the old "both accepted implies commit" rule intact three paragraphs above and in the activation matrix, so the document contradicted itself on the exact point the review raised. The obsolete text is replaced rather than supplemented. The recovery outcome is also made executable: the new secret is returned once and startup/status holds no management authority, so recovery cannot "resume" anything — it stops with evidence intact, and the next rotate, which does carry transient authority, confirms the stranded rotationId's abort before starting over. Bookkeeping: the earlier pass introduced a duplicate P2-A11 and appended three P6 rows after the verification section instead of into the activation matrix. The plaintext-bootstrap row is renumbered P2-A21 and the rotation rows are folded into the matrix, replacing the stale uncertain-commit row.
Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: jun <jun@lidge.dev>
…hout a configured chain (#3239) An encrypted V2 worker payload needs the native ChatGPT backend, but applySubagentModelFallback only consulted a fallback chain the operator configured. With no chain, a routed sub-agent model reached the encrypted-task guard and failed with unreadable_encrypted_agent_task. When nativeFallbackOnly is set and no chain exists, build the chain from DEFAULT_SUBAGENT_MODELS. selectAvailableSubagentModel still drops every non-forward candidate and isSubagentModelUnavailable still honours disabled models, quota, and health. Ordinary routed spawns are unchanged. Source hunks from #3228; the bundled GUI fallback-chain editor is left for its own feature PR. Co-authored-by: jun <jun@lidge.dev> Co-authored-by: x3M3x <amroeid1999@gmail.com>
…d native chain (#3240) #3239 synthesized a DEFAULT_SUBAGENT_MODELS chain for an unreadable encrypted spawn when the operator configured none. That chain fires in the first fallback pass, before recoverEncryptedAgentTask, so with agentTaskRecovery enabled the spawn was rerouted to native gpt-5.5 and recovery was skipped along with its caller-auth, proxy-secret and token-validity gates. tests/agent-task-recovery-security.test.ts went 13/13 -> 2/13 on dev. Synthesize the chain only when recovery is not enabled. An operator who enabled recovery chose to decrypt and stay routed; a configured chain keeps its precedence either way. Regression: recovery enabled + no chain + nativeFallbackOnly -> no fallback (red without the guard); the 13 recovery security cases are green again. Co-authored-by: jun <jun@lidge.dev>
Codexless's app-server sends originator=codexless_agent, which the encrypted V2 recovery admission allowlist rejected, so its child tasks failed as unreadable_encrypted_agent_task. Admit that originator. The issuer, client id, token, account and proxy-secret checks after admission are unchanged. Regression: the recovery request preserves originator=codexless_agent. Source from #3229. Co-authored-by: jun <jun@lidge.dev> Co-authored-by: iamnomankazi <60215267+iamnomankazi@users.noreply.github.com>
…awns (#3239, #3240) (#3242) * Revert "fix(subagents): let encrypted-task recovery run before the synthesized native chain (#3240)" This reverts commit 7f00d0e. * Revert "fix(subagents): auto-fallback encrypted V2 spawns to native Codex without a configured chain (#3239)" This reverts commit 744d12d. --------- Co-authored-by: jun <jun@lidge.dev>
…bel every row (#3222) * docs(cursor): diff-level roadmap for unified Cursor model identity One published row per Cursor base with thinking/fast/1M as dimensions, a Codex Fast toggle that reaches Cursor's fast variant, and a global switch that exposes -fast identities to clients without a toggle. Docs-only work-phase (wp1) of a four-phase unit. Contains 000_plan (work-phase map + measured current state + RUN verifier table), 001_current_state (why the picker never reads CURSOR_CAPABILITIES, where Codex Fast dies for Cursor), 002_audit_round1 (10 blockers from two review lanes, all folded), and diff-level decade docs 010/020/030 for the three implementation phases. Notable audit findings folded before any code: provider-level supportsServiceTier short-circuits before the per-model map; tierLogForRunTurn runs BEFORE runTurn so telemetry must recompute the variant rather than rebuild a non-pure request; usage/log.ts and usage/cost.ts read wireKind by string comparison and are invisible to tsc. Refs devlog/_plan/260902_cursor_unified_identity * feat(cursor): derive the picker seed from the capability table and label every row CURSOR_STATIC_MODELS was a hand-maintained list that drifted from CURSOR_CAPABILITIES: cursorUmbrellaRows() existed but only tests called it, so collapsing a variant changed routing without changing what Codex listed. The seed now derives from that function plus two declared lists for ids with no capability record, so the two can no longer disagree. Cursor rows also showed raw slugs (cursor/kimi-k3) because routedDisplayName passes a routed slug through unchanged and nothing carried Cursor's labels into the provider config. ProviderRegistryEntry had no modelDisplayNames field at all; the consumer (configuredModelDisplayName) already existed. Wire it through providerConfigSeed and enrichProviderFromRegistry, the latter per-model so an existing install picks up labels without losing an operator rename. The row set is unchanged (54 ids, none added or dropped) - this is a refactor of where rows come from, plus labels and three corrected windows (gemini 1048576, gpt-5.5-extra 200000) where the capability table was approximating the seed. Fixes the frozen row-count assertion that went red when claude-fable-5-1 was seeded in 5fc7d07: it now derives the expected count instead of hard-coding it. Refs devlog/_plan/260902_cursor_unified_identity/010_wp2_umbrella_seed.md * docs(cursor): park wp2/wp4 residuals in a numbered doc The roadmap named residuals in prose with no home (audit B14): effort ladders on a listed fast id, claude-4-sonnet-1m staying a real row, fastMode carrying two meanings, and the five pre-existing test failures that reproduce on a clean stash of this branch. Each records what would change the decision, so a later cycle does not rediscover them as new findings. Refs devlog/_plan/260902_cursor_unified_identity/040_residuals.md * docs(cursor): record the measured -fast round-trip that proves audit B11 The reviewer's claim that a bare -fast suffix picks the wrong dimension was not theoretical. Measured: claude-opus-5-fast resolves to claude-opus-5-high-fast (clamped, and in the quarantined regular family) while the Codex toggle would send claude-opus-5-thinking-max-fast. The mirror case is just as wrong -grok-4.6-thinking-fast degrades to a bare grok-4.6 with no effort and no fast marker, because grok has no thinkingFast spec. Either fixed suffix is wrong for half the table, which is why cursorFastIdFor composes from the base's defaultVariant. --------- Co-authored-by: jun <jun@lidge.dev>
…els list (#3230) * feat(server): advertise api_types and capabilities on the raw /v1/models list Cursor's local-agent runtime (the Private Inference build) enables its reasoning-effort control only when a model row carries api_types and, optionally, a capabilities object. Emit both on every row of the OpenAI-shape list: api_types is constant (chat_completions, responses, anthropic_messages; membership is load-bearing for Cursor's wire selector and guarded by a unit test), capabilities carries context_length, supports_vision and the reasoning_effort ladder when the catalog knows them. Plain OpenAI clients, Grok Build and the Codex catalog branch ignore the new keys. The combo e2e assertions that compared whole row literals now match on the combo-relevant shape while keeping is_combo presence explicit. * fix(server): include output_modalities so Cursor keeps enriched rows Cursor's local-agent runtime drops any api_types row whose capabilities lack output_modalities containing "text", which silently disabled the effort control on every row. Emit output_modalities: ["text"] and mirror input_modalities when the catalog knows them. Verified live in Cursor Private Inference 3.18.25: the picker shows Low/Medium/High/Extra High for gpt-5.6-sol and a High turn arrives as reasoning.effort=high on /v1/responses. * fix(server): report the effective context window, not the provider cap contextWindow is already narrowed by providerContextCaps; contextCap is the raw operator knob and is set on every row of a capped provider even when the cap did not bite, so preferring it over-reported models whose real window sits below the cap. Freeze the shared api_types constant and copy it per row. * fix(server): omit context_length when it floors to zero positiveInt accepted 0.5 and then floored it to 0, which would emit an invalid zero context length instead of omitting the field (CodeRabbit). * feat(server): advertise the native long-context tier for Cursor Max Mode Cursor's local-agent runtime shows a Context selector (default vs long window, long marked as costing more) when a model row carries a long-context threshold below its context_length. It reads that threshold only from pricing.overrides[].min_prompt_tokens; a cost.long_context object fails its row schema. For native GPT-5.6 rows advertise the family's 272k default and 922k opt-in pair through nativeOpenAiContextTier; routed rows have no separate tier and stay unchanged. * fix(catalog): drop the native long-context tier under any window lever below it A per-model window override or provider window below the long window must remove the tier the same way a provider cap does; otherwise the row keeps advertising 922k while the effective window is smaller (CodeRabbit). --------- Co-authored-by: jun <jun@lidge.dev>
* docs: Cursor Private Inference connector guide Explain why regular Cursor needs a public tunnel (its backend calls the custom base URL), how Cursor's local-agent build reaches opencodex on loopback instead, the per-OS environment mechanics for a GUI-launched app on macOS, Windows and Linux, which effort ladders Cursor exposes per model id, and how to verify. The guide states that opencodex does not distribute that build and links nothing to download. * docs(cursor): explain Max Mode as the Context selector and effort max as unreachable * devlog: Cursor local models schema unit (research, audits, live evidence) --------- Co-authored-by: jun <jun@lidge.dev>
…3225) Codex's Fast toggle is OpenAI's service_tier field, and Cursor has no such field - its fast product is a different model variant (claude-opus-5-thinking-high-fast) or a {id:fast} request parameter for Grok. So a service_tier on a Cursor route was silently dropped, and no Cursor row could advertise the toggle at all: FAST_WIRE_ADAPTERS is a closed set that Cursor was not in. Add a cursor-variant FastWire kind, declare it on the Cursor registry entry, and have the request builder consume the tier DECISION (not the raw caller field, so fastMode=false still suppresses a caller's request). A thinking umbrella pick upgrades to thinkingFast rather than the regular-fast sibling, which is a different product with a shorter ladder whose regular family is quarantined for claude-opus-5. Only the five bases that actually declare a fast variant advertise the tier, so there is no dead toggle. That required one more fix: forwardCallerTier ignored the wire's foreignCallerTiers declaration, so an unclassified cursor-variant route projected 'unknown' support - enough for Codex to offer a toggle on kimi-k3, which has no fast variant at all. Telemetry recomputes the variant from the same pure inputs the builder uses. Rebuilding the request there would be wrong twice over: tierLogForRunTurn runs before runTurn, and createCursorRequest mints conversation ids. Also widens the usage/log.ts wireKind allowlist, which silently returns null - dropping the whole tier row - for a kind it does not recognise. Refs devlog/_plan/260902_cursor_unified_identity/020_wp3_codex_fast_toggle.md Co-authored-by: jun <jun@lidge.dev>
…sion audit (#3218) * docs(devlog): open the bug/PR closeout stack roadmap * docs(devlog): fold the A-gate import-boundary finding into phase 5 * docs(devlog): record the #3163 and #3166 landings * docs(devlog): record why #2986 does not land in this train * docs(devlog): close out the bug/PR closeout stack * docs(devlog): record the final green CI verdict on dev * docs(devlog): open the bug-label drawdown roadmap with audit corrections * docs(devlog): record the Batch A landings and first rebase carry * docs(devlog): record the Batch B rebase carries * docs(devlog): record why the rebase service earned its keep * docs(devlog): record the Batch C rebases and the one real review finding * docs(devlog): record the #2999 scope boundary that survived execution * docs(devlog): record Batch D - every bug PR closed * docs(devlog): record what the PR half of the campaign cost * docs(devlog): replan the remaining issues to one per cycle * docs(devlog): carry the i3141 evidence into the replan * docs(devlog): diagnose i3141 - fix predates the reported version * docs(devlog): retire the second bundle * docs(devlog): record the i3141 re-triage action and outcome * docs(devlog): diagnose i3152 log table jitter * docs(devlog): i3152 - measurement disproved the layout diagnosis * docs(devlog): diagnose i3136 slashed-id price lookup * docs(devlog): diagnose i3150 citation marker passthrough * docs(devlog): diagnose i3155 capacity plan allowlist * docs(devlog): i1419 stays open pending crash frames * docs(devlog): record the i1419 re-triage ask * docs(devlog): diagnose i2999 publication overwrite race * docs(devlog): record the i2999 outcome and remaining scope * docs(devlog): diagnose i2813 as a client-side reserve gate * docs(devlog): diagnose i1527 residuals as trace-blocked * docs(devlog): correct i1527 envelope-cap wording (192 blobs, HTTP 400) * docs(devlog): plan p3193 loopback alpha-search reimplementation * docs(devlog): record p3193 landing (#3205 -> 53c09a2) * docs(devlog): plan the main->dev regression audit * docs(devlog): pin regaudit counts, add tests-only/security passes and the exact-head dispatch * docs(devlog): record regaudit reviewer verdicts * docs(devlog): record the exact-head dev CI verdict and Windows classification * docs(devlog): record the main control run proving the Windows failures predate the range * docs(devlog): record the pass-1 recount and the #3217 root cause * docs(devlog): plan i3217 (Spark functions-namespace flattening) * docs(devlog): record i3217 landing (#3224 -> d23eab4) * docs(devlog): regaudit2 recount and disposition table * docs(devlog): regaudit2 CI verdict on d23eab4 and the four PR arrivals * docs(devlog): plan p3226 (scoped namespace scrub) * docs(devlog): p3226 audit finding and carry plan * docs(devlog): record p3226 landing (#3234 -> b732b0d) * docs(devlog): plan p3227 (combo zero-output incomplete failover) * docs(devlog): record p3227 landing * docs(devlog): plan p3228 (encrypted V2 spawn native fallback) * docs(devlog): record p3228 landing * docs(devlog): plan p3229 (Codexless originator in task recovery) * docs(devlog): record p3229 landing and the #3239 regression repair * docs(devlog): r3239 regression repair record * docs(devlog): r3239 audit note * docs(devlog): record p3232 (merged by maintainer) * docs(devlog): p3232 verification result * docs(devlog): regaudit3 recount and landing table * docs(devlog): record the #3239/#3240 revert and correct the #3228 disposition * docs(devlog): rv3239 revert record * docs(devlog): rv3239 audit note * docs(devlog): regaudit3 second-dispatch verdict * docs(devlog): regaudit3 recount refreshed (#1419 closed by maintainer; count 4) * docs(devlog): regaudit3 final CI verdict and c-7 --------- Co-authored-by: jun <jun@lidge.dev>
#3233) * feat(cursor): expose -fast identities to clients without a Fast toggle Codex has a Fast toggle, so its rows stay umbrella rows and the toggle picks the dimension. Claude Code and other OpenAI-compatible clients have none - they can only pick a listed id - so with fastMode on they are offered the fast identity directly. The listed id is composed from the base's defaultVariant, not a bare -fast suffix. Measured: claude-opus-5-fast parses back as the REGULAR-fast sibling and resolves to claude-opus-5-high-fast, a shorter ladder in the quarantined regular family, which is a different wire from what the Codex toggle sends. The mirror case is equally wrong: grok has no thinkingFast spec, so grok-4.6-thinking-fast would fall back to the regular spec and emit a bare grok-4.6 with no effort and no fast marker. Either fixed suffix is wrong for half the table. A test asserts the two surfaces converge: for every fast-capable base, the listed id and the toggled umbrella id resolve to the same wire. Request-time promotion needed no new code. fastMode already produces a set tier decision on a fast-capable route with no caller service_tier, and every non-Codex inbound path replays through handleResponses, so PR2's builder already promotes a client whose saved config still names the umbrella id. Desktop 3P aliases and dashboard row ids are deliberately untouched: the former are hashed from the model name, the latter are enable/disable keys. Refs devlog/_plan/260902_cursor_unified_identity/030_wp4_global_fast_switch.md * perf(cursor): resolve the fast-id helper once per request, not per model The listing branches called await import() inside the row mapper, so a request with N models paid N dynamic imports on a hot path. Hoist it to one resolution per request; when the switch is off the value is null and the adapter module is never loaded at all. Raised in review of #3233. --------- Co-authored-by: jun <jun@lidge.dev>
) The three Cursor identity PRs are on dev. Records each merged head and squash commit with the ancestry proof, and notes that --admin cleared only the review requirement - every merged head had zero failing checks. Also closes R5: the agent-task-recovery red was dev's own, and dev's #3242 (revert of #3239/#3240) fixed it. That file is 19/19 on the landed dev, so the follow-up fix PR this unit was going to open is unnecessary. Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: jun <jun@lidge.dev>
…ard (#3247) * feat(integrations): read-only Cursor Private Inference status route GET /api/native-integrations/cursor reports which Cursor builds are installed (product.json nameLong tells Private Inference from regular Cursor), the two values to paste into Cursor's gateway form, whether a Cursor client has called /v1/models since the proxy started, and which active models will show Cursor's Reasoning and Context controls. Nothing is written to Cursor: its settings live in a database the running app rewrites and its key in the OS keychain. The last-seen recorder keeps a validated User-Agent and a timestamp in memory only. * fix(cursor): filter the prediction table by catalog visibility The status route built its Model/Reasoning/Context prediction from the unfiltered catalog, so a model disabled in opencodex still appeared in the dashboard while the raw /v1/models list Cursor reads omitted it. Apply the same filterCatalogVisibleModels pass; regression test drives both endpoints with a disabled model. * docs(skill): the Cursor status route reports both builds, not only Private Inference --------- Co-authored-by: jun <jun@lidge.dev>
* feat(gui): Cursor integration tab with detection, gateway values, connection state Adds a read-only Cursor tab to the Integrations page after Grok Build. The page reads GET /api/native-integrations/cursor and shows which Cursor builds are installed (Private Inference vs regular), the two gateway values Cursor's own form wants with Copy buttons, whether a Cursor client has called the proxy since start, and the model/reasoning/context table Cursor will render. The overview grid gains a Cursor card whose 'applied' state means a Cursor request was seen within 24h, since the proxy never writes into Cursor. The DSH tab label shrinks to 'DSH' to make room on the strip; the full product name stays on the API Keys page. Nine locales carry the new keys; locale-parity and integrations-surfaces tests updated, plus a new cursor-integration-page suite (12 tests). * fix(gui): translate the Cursor tab in six locales, link the guide from the warning, test interactions Reviewer findings on 9ebb4fce9: - de/fr/ja/ru/tr/zh carried the English strings verbatim; key-set parity cannot see that. All non-brand Cursor keys are now translated and locale-parity gains a guard scoped to integrations.cursor.* for every locale, with the brand/cognate exceptions listed explicitly. - The regular-Cursor warning promised a guide but linked none; the guide anchor now sits inside the notice. - Tests asserted presence only: Copy now proves clipboard.writeText and the label flip, the API Keys button proves the hash change, and the polling test proves subscriber membership before and after unmount. * fix(gui): scope the Turkish 'Model' cognate exception to tr; natural Japanese for the reasoning ladder * test(gui): count the Cursor row in the overview totals and allowlist its brand labels in fr CI shard 2 and gates caught two files outside the focused set: the overview row-count expectations (four native rows became five) and the French accidental-English guard, which needs the same four Cursor brand keys the zh-TW and per-locale guards already list. --------- Co-authored-by: jun <jun@lidge.dev>
* docs(cursor): describe the dashboard Cursor tab and mark it as a read-only surface Adds a 'From the dashboard' section to the Cursor Private Inference guide covering the four cards (installed builds, gateway values including the credential-mode branch, connection via /v1/models + Cursor/ UA, model table) and rewrites the integrations guide paragraph that said Cursor had no tab. The 'not switches' section grows to five surfaces in en/fr/tr/zh-tw. * docs(cursor): match the dashboard section to the shipped route Reviewer findings: regular Cursor shows path only; Base URL comes from the runtime port record, not the dashboard's request port; the seen recorder accepts exactly Cursor/<version>; the prediction table follows catalog visibility (now also true of the route). * devlog(cursor): 040 publish plan for the three-PR stack * devlog(cursor): 040 — restack after each squash, security lane, resolved heads * devlog(cursor): 040 — security verdict gates PR 1, bypass comment before each admin merge --------- Co-authored-by: jun <jun@lidge.dev>
…test teardown (#3257) * devlog(windows): CI failure inventory and repair roadmap (000-040) * devlog(windows): audit fixes — test filename, win32 platform seam in shutdown test, exact line cites * fix(windows): server.stop drains the config-dir ACL flight it started hardenConfigDir() spawns icacls.exe as a fire-and-forget flight (e5d5886). The child holds the config directory open until it exits, and Windows locks are mandatory, so removing the directory right after a clean server.stop() returned EPERM/EBUSY — which is what every Windows CI shard has hit since 2026-08-30 in the account-store, auth-api and live-server fixtures. flushConfigDirHardening(dir) is now a production function scoped to one directory; startServer captures its config dir before loadConfig and the composite stop() awaits the flight after the listeners close. Regression test drives stop() with a held flight and proves it stays pending until release (driven red before the fix). * test(account-store): per-test scratch home, both icacls runners stubbed, retrying teardown The fixed repo-local .tmp-codex-accounts-test meant one EPERM teardown poisoned every later case (49/49 errors in run 33590540220 were hooks). The sync-only icacls stub also no longer covered hardenConfigDir(), which uses the async runner since e5d5886. * test(windows): retrying scratch-home teardown across the live-server suites; per-test homes for auth-api and oauth-status Fourteen suites removed their scratch home with a bare rmSync right after server.stop(). Even with stop() now draining the ACL flight, antivirus and indexer handles on a fresh temp dir are real on the hosted image, so every teardown goes through removeTreeWithRetry (EPERM/EBUSY/ENOTEMPTY, bounded). codex-auth-api and oauth-status-privacy also move off fixed repo-local dirs and stub both icacls runners, the same shape as codex-account-store. * fix(windows): drain the ACL flight even when an earlier shutdown release rejects Reviewer P2: the flush sat after backgroundLifecycle/native-main release in one finalizer, so a rejected release skipped it and the EPERM/EBUSY window came back on the failure path. The flush now runs in finally; the original rejection still propagates. Regression test mocks a throwing native release and proves stop() stays pending until the flight settles (driven red). --------- Co-authored-by: jun <jun@lidge.dev>
…wn codemod (#3258) * fix(windows): fsync token and spill files through a writable handle; bump test spawns a real path Windows rejects fsync on a read-only fd with EPERM. service-secrets opened 'r' for every backup/replace/restore, so all three token-ownership cases and the connected-key-rotation child failed on windows-latest; spill-store did the same on its exclusive-copy fallback. Both now open 'r+' (asserted via an openSync spy, driven red). tests/bump-dev-version used new URL(...).pathname, which is '/D:/a/...' on Windows, so bun could not load the CLI and every case exited 1 — including the malformed-input case, which read that as a correct rejection. fileURLToPath + process.execPath, stderr in the failure message, and the specific rejection text asserted. * test(responses-state): hermetic platform lanes; release the ACL gate in finally Generic spill/admission cases assert the synchronous lane, so they pin the platform to linux; a Windows host routed them through queued async publication and they read a resident where a stub was expected (~45 cases). Windows-lane cases now inject both principal resolvers alongside the platform: on the hosted runner the real PowerShell lookup failed with EACLIDENTITY before the case reached its own seams. That failure happened outside the try/finally in 'prices the job-owned superseded generation', so the held icacls promise was never released and the next case waited for it until Bun's 60 s ceiling. spill-store.harden() now consults windowsSecretAclApplies() on both lanes so the platform seam reaches the sync harden too. * test(windows): stub icacls in service.test, settle async spills in issue-702, size the first child wait Dispatch 33595585136 (first run with the ACL lifecycle fix): windows 1/4 fell from ~120 failures to 8 and the account-store/auth-api cascades are gone. What remained: service.test.ts hands a synthetic SID to a REAL icacls on a Windows host (EICACLS on every saveConfig) — both runners are now stubbed at file scope; issue-702 unlinked a spill that Windows had not yet published — it awaits the publication first; native-profile-manager's crash case waited a private 5 s for the first child boot of the shard — it now waits inside its own 15 s budget and reports the child's stderr on timeout. * test(responses-state): gate only spill hardens; copy fallback follows the platform seam Dispatch 33595585136 shard 2 left two failures in this file. The shutdown budget case gated EVERY async icacls call, and on a real Windows host the snapshot flush hardens responses-state.json through the same runner, so flushResponseState() sat behind the gate until the 30 s ACL deadline and the case hit Bun's 60 s ceiling. Gated runners now pass non-spill targets through. canUseExclusiveCopyFallback keyed on process.platform, so a case pinned to the POSIX lane on Windows turned an injected link failure into a successful copy and asserted a tombstone that never came; it now uses the same seam as harden(). * test(responses-state): win32 admission variants; assert the propagated shutdown rejection Reviewer P2: pinning the admission suite to the sync lane left the Windows async branch (runPendingResponseSpill post-write ceiling, failure tombstone) without a case. Two win32 variants now cover it after the queue settles; the write-failure variant scopes the injected failure to the oversized candidate because the queued candidate counts against the RAM cap while it waits. P3: the rejected-release regression now asserts the original error message. * devlog(windows): 011 — dispatch rounds, reviewer verdicts, fuck-powershell cases * test(native-main-refresh): yield to the event loop while waiting for the abort listener The wait spun on Promise.resolve(), which keeps the microtask queue non-empty and never lets a subprocess exit callback run. On Windows the exclusive claim can harden its lock file through an async icacls child before it reaches the abort listener, so the spin never ends. Dispatch 33597649234 shard 4 sat in this file for eight minutes until the job ceiling with zero failures logged. * test(server-stop): replace the 60 ms pending oracle with an observed listener close Reviewer P3: the shutdown-drain regression assumed all earlier stop() stages finished within 60 ms. It now polls until the listener refuses connections, so 'still pending' can only mean the held ACL flight. * test(windows): yield in the remaining claim-cancellation waits; retrying teardown in oauth-manual-code and provider-quota Dispatch 33601508392 shard 4 hung again in responses-native-main-refresh — the two WebSocket string-abort cases spun on Promise.resolve() like the one fixed in 2bf189d. Both now yield to the event loop. Two more bare rmSync teardowns (oauth-manual-code fixed dir, provider-quota temp homes) go through removeTreeWithRetry. * test(windows): freeze the spill ACL clocks in the shutdown-fallback cases; let the write-lock holder outlast a slow contender boot Now that harden() follows the platform seam, the synchronous fallback harden really runs on every host, and its 80 ms reserve raced a loaded Linux shard's wall clock (run 33603770447 test 3/4). The three fallback cases inject the same frozen clock the budget case already used. codex-write-lock's holder used the helper's default 3 s ceiling; a windows- latest contender took longer than that to boot, so the hold expired first and the contender read 'acquired'. The ceiling is now 20 s — the release marker still ends the hold immediately. * test(oauth-manual-code): per-test scratch home with both icacls runners stubbed The manual-code login test persisted its credential through the real icacls on windows-latest and settled as 'OAuth authentication failed' (run 33603770447 shard 4). Same shape as codex-account-store / codex-auth-api: this file is about the PKCE flow, not ACLs. * test(retained-root): size the child marker wait inside the test budget A bun --eval child reaches its marker in 8-11 s on windows-latest; the 10 s default wait lost the race on run 33605898170 shard 1 while the same case passed at 10.4-10.6 s on earlier runs. * test(windows): route every recursive scratch-tree removal through removeTreeWithRetry Mechanical codemod (script kept in .tmp): 870 rmSync(recursive) teardown sites across 381 test files now use the bounded EPERM/EBUSY/ENOTEMPTY retry. Each Windows dispatch so far surfaced one or two more bare rmSync sites (oauth- reauth-bind, responses-native-main-refresh, provider-quota, ...); this closes the class instead of chasing it a shard at a time. 18 sites in 8 files skipped on purpose: mixed file/dir collections, existing custom retry helpers, and the ACL fixture whose shape covers single files. tsc green; test:changed 14165 pass / 2 lab-fabric cases that pass in isolation. * test: fail the listener-close poll closed; per-call marker deadlines inside each test budget Reviewer P2s: the shutdown-drain oracle fell through after 200 attempts without asserting the listener was closed, so a still-open listener could pass as 'pending on the ACL flight'; it now asserts refused. retained-root's 16 s default exceeded the 15 s budget of its first caller, so Bun's timeout, not the helper, would report a slow child; every caller now passes an explicit deadline that fits its own budget. * test(windows): stub icacls in oauth-reauth-bind; widen the startup child port wait Dispatch 33610501053 shard 4: oauth-reauth-bind has no server, so nothing drained the OAuth store's ACL flight before teardown and the icacls child outlived the retry window; both runners are stubbed and the flight flushed. native-profile-startup's child needed 17 s to reach its port file on the loaded shard; the wait is now 18 s inside 20 s+ budgets. * test(oauth-public-surface): stub icacls and flush the ACL flight in a server-less OAuth store test --------- Co-authored-by: jun <jun@lidge.dev>
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. |
|
Owner admin merge, user-authorized promotion. Tree == origin/dev a6ee24f. enforce-target draft conversion is the known ALLOWED_BASES=[dev] behaviour for promotion PRs. |
|
Important Review skippedToo many files! This PR contains 967 files, which is 667 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. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Team Run ID: ⛔ Files ignored due to path filters (8)
📒 Files selected for processing (967)
You can disable this status message by setting the 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 |
|
✅ Deterministic PR hygiene checks passed. |
⏳ DRAFT
What to do
Its title has been prefixed with |
리뷰 · 우선순위 75 / 80이 PR은 기여자 기능 PR이 아니다. 당시 리뷰 시점의 현재 싣는 화물(이미 당시 봇이 제목에 실제로 이 PR은 2026-09-02 18:37 KST 에 admin merge 로 types.ts/config.ts 분할을 이유로 닫을 대상이 아니다. 중복 승격도 아니다. 안정 채널이 목적이다. 라벨은 바꾸지 않는다. 이 댓글은 이미 머지·태그·npm 퍼블리시까지 끝난 뒤의 사후 기록이다. 경로 base 메인테이너의 판단이 필요한 지점
너의 추천 이 댓글은 grok-bot이 작성했습니다 |
Summary
dev(a6ee24f5b, 179 commits since v2.39.0) intomainfor v2.40.0. Tree byte-identical toorigin/dev(git diff --stat origin/dev HEAD→ empty;package.jsonalready carries 2.40.0).Verification
workflow_dispatchon the stack tip: shards 1/4, 2/4, 3/4 SUCCESS on four consecutive runs (33597649234, 33601508392, 33605723635, 33610501053); Linux ×4, macOS, keyring ×3, npm-global ×3 green on each and on the dev push run 33592329682.devlog/_plan/260902_bug_label_drawdown/071_regaudit_landing.md(four reviewers, no regression) anddevlog/_plan/260902_windows_ci_release/.7fd141f2a). User-authorized admin merge;release.ymlstill gates on the push-event CI for this SHA.Checklist