Skip to content

[WRONG BRANCH] release: v2.41.0-preview.20260903 - #3339

Closed
lidge-jun wants to merge 38 commits into
previewfrom
codex/promote-preview-2410
Closed

[WRONG BRANCH] release: v2.41.0-preview.20260903#3339
lidge-jun wants to merge 38 commits into
previewfrom
codex/promote-preview-2410

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

Verification

Release preflight run before the bump, each check blocking:

  • 2.41.0-preview.20260903 unpublished on npm (404).
  • No v2.41.0* tag on the remote.
  • No GitHub release v2.41.0.
  • Moves preview forward: current dist-tags are latest=2.40.0, preview=2.40.0-preview.20260902.

Evidence for the delta itself, all at the dev head: 1387 focused assertions across 38 test files with zero failures, bun run typecheck exit 0, bun run privacy:scan passed. PR #3338 carried the same tree through full CI green on all 24 checks.

The local full suite was not run at the maintainer's explicit instruction for this unit, which is also why scripts/release.ts is not the vehicle here — its preflight runs that suite. The documented manual path in 030_wp3_preview_release.md is used instead, and exact-SHA push-event CI on preview is the gate before dispatch.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Screenshot

This promotion carries the Meta provider mark from #3338. The provider rail before and after, on both tile surfaces:

Meta provider mark, before and after, dark and light

Summary by CodeRabbit

  • New Features

    • Added Gemini 3.8 Flash support and updated Antigravity defaults.
    • Added Meta Model API and Meta Muse Code provider support, including model metadata and OAuth safeguards.
    • Added optional Cursor effort-row model selectors and richer integration status details.
    • Added atomic provider configuration updates with conflict and security validation.
    • Added configurable catalog download timeouts and improved dashboard URL detection.
  • Bug Fixes

    • Improved usage reporting accuracy for complete ledgers and large histories.
    • Improved combo failover, quota handling, log-table stability, and legacy tool compatibility.
    • Added pull-request attribution checks and contributor-credit documentation.

lidge-jun and others added 30 commits September 2, 2026 18:43
…rkflow call (#3262)

Both v2.40.0 release dispatches (33615174183 preview, 33615177849 main) died
at startup_failure: a workflow_call cannot grant its callee more than the
calling job holds, and dev-version-bump.yml's job declares contents+pull-
requests write. #3129 wired the call but never dispatched a release, so this
is its first live run. The caller job now declares exactly the callee's two
permissions; no other job in release.yml gains anything.

Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(release): pass the bump job's permissions through the reusable-workflow call

Both v2.40.0 release dispatches (33615174183 preview, 33615177849 main) died
at startup_failure: a workflow_call cannot grant its callee more than the
calling job holds, and dev-version-bump.yml's job declares contents+pull-
requests write. #3129 wired the call but never dispatched a release, so this
is its first live run. The caller job now declares exactly the callee's two
permissions; no other job in release.yml gains anything.

* devlog(windows): 070 outcome — v2.40.0 released, Windows shards repaired, follow-ups

* devlog(windows): 070 — correct the service-lifecycle trigger note

* devlog(windows): 070 — record the full 4/4 Windows-green dispatch on the released dev tip

* chore(devlog): wp5 release closeout marker

---------

Co-authored-by: jun <jun@lidge.dev>
…toggle (#3269)

* devlog(release): 080 — bot PR toggle provenance and decision, service-lifecycle trigger gap

* chore(devlog): wp1 provenance closeout marker

* devlog(release): 080 — toggle applied via REST, read-back true

* ci(service-lifecycle): trigger on release.yml changes so the release gate cannot dead-end

release.yml requires a service-lifecycle run for the release SHA when any
gated path changed since the previous tag, but a release-branch commit that
touched only release.yml (the v2.40.0 permissions carry, #3263/#3264) matched
no lifecycle trigger path, so both dispatches needed a manual run. The path
is now in both trigger lists and the gate regex; tests/ci-workflows keeps the
three in sync.

---------

Co-authored-by: jun <jun@lidge.dev>
…e_effort_table wp0) (#3272)

Co-authored-by: jun <jun@lidge.dev>
…led bundle (#3273)

* docs(devlog): Cursor bundle effort-table roadmap (260902_cursor_bundle_effort_table wp0)

* feat(cursor): read Cursor Private Inference effort table from the installed bundle

Predict the Reasoning ladder from the table compiled into the detected
Cursor Private Inference install, with the static 3.18.25 mirror as
fallback, and expose the provenance on the integration status route.

* fix(cursor): reject partial effort tables and guard the bare gpt-5 regex; gate gemini on supports_reasoning

* fix(cursor): bind the bare gpt-5 rule by shape, not minified identifier

---------

Co-authored-by: jun <jun@lidge.dev>
…able 5.1 seeds (#3275)

* refactor(cursor): canonical Claude-id normalizer replaces the three Fable 5.1 seeds

Every Claude spelling Cursor has used (claude-fable-5-1, claude-fable-5.1,
claude-5.1-fable, with -thinking/-fast/effort suffixes) resolves to one
capability base; wire ids are composed back in the spelling the live
GetUsableModels roster exposed, else the spelling the saved config used.

* fix(cursor): publish live Claude spelling and Max-Mode evidence only after the cache accepts the capture

---------

Co-authored-by: jun <jun@lidge.dev>
* feat(models): advertise max_output_tokens on /v1/models rows

Carry an authoritative output ceiling (live capabilities, generated
metadata, or a narrowing model-scoped config value) through the catalog
and emit capabilities.max_output_tokens for native, routed and combo rows.
The long-context threshold stays in pricing.overrides: Cursor derives its
own top-level field from that and never reads a raw one.

* fix(models): reject unsafe-integer output ceilings on /v1/models rows

---------

Co-authored-by: jun <jun@lidge.dev>
…effort table (#3276)

* docs(devlog): wp3 amendments c,d

* feat(cursor): opt-in effort-variant rows for models outside Cursor's effort table

cursorEffortRows (default off) publishes one <id>--<effort> row per
supported effort for models Cursor Private Inference renders no Reasoning
control for, and resolves the base model plus effort from that id on
/v1/responses, /v1/chat/completions and /v1/messages. Off, /v1/models is
byte-identical.

* test(cursor): avoid a token-looking fixture credential

* fix(cursor): effort rows reject --none, skip detection for plain ids, normalize before combo dispatch

---------

Co-authored-by: jun <jun@lidge.dev>
…able-less rows (#3277)

* feat(gui): Cursor tab shows effort-ladder provenance and a hint for table-less rows

The Integrations > Cursor card names whether the Reasoning ladders came
from the installed Private Inference bundle (and which version) or the
static mirror, marks rows Cursor renders no control for, and points at
cursorEffortRows / modelDefaultReasoningEfforts. en + ko strings; the
other locales carry the en text because the catalogs are type-checked.

* test(gui): cover Cursor tab provenance, table-less hint and effort-row counts

* i18n(gui): translate the Cursor provenance strings; allowlist the em-dash marker

* gui(cursor): honest static-fallback wording; separate single-window marker from the no-control marker

---------

Co-authored-by: jun <jun@lidge.dev>
…e, bundle table, effort rows (#3278)

* docs(cursor): identify the Private Inference build, gateway precedence, bundle table, effort rows

The guide now tells the reader how to tell the local-agent build from
regular Cursor (product.json nameLong), where the effort table lives and
that opencodex reads it from the install, how the saved gateway and the
CURSOR_LOCAL_AGENT_* environment rank, the real header format, the two
ways a table-less model still gets an effort (cursorEffortRows and a
provider default), and the /messages wire caveat for Claude effort.

* docs(cursor): effort-row selector wins over an ocx-effort directive

---------

Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
)

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…nd the sidecar (#3286)

* docs(devlog): Gemini 3.8 Flash rollout roadmap (260903_gemini_38_rollout wp0)

Diff-level roadmap for rolling Gemini 3.8 Flash across every surface that
currently names 3.6/3.7.

Two findings shape the plan, both first-hand rather than inherited from the
3.7 unit:

- Google documents 3.7 Flash as "remains fully supported", and a live CCA
  fetchAvailableModels call returns 3.5, 3.6, 3.7 and 3.8 together. So this
  is an ADDITIVE rollout with a default move, not the hard replacement the
  3.6 to 3.7 migration performed.
- CCA publishes gemini-3.8-flash-{low,medium,high} and no -tiered row, so 3.8
  is structurally 3.6-shaped: it belongs in ANTIGRAVITY_EFFORT_WIRE_MAP, not
  in the single-wire thinkingLevel map 3.7 uses.

Three adversarial audit rounds (independent gpt-5.6-sol reviewer at high
effort) took this from FAIL to PASS. The two most valuable findings were
confirmed against the live backend: the Claude SDK identity paragraph 429s on
3.8 exactly as on 3.7, so that strip guard must widen before 3.8 becomes the
default; and static vs discovered effort resolution returned different request
bodies for the same input.

Docs only. No runtime change in this commit.

* feat(antigravity): expose Gemini 3.8 Flash with its suffix effort ladder

Google shipped Gemini 3.8 Flash on 2026-09-02 and CCA already returns it
ranked first in the Recommended sort, but discovery published it as three
uncollapsed rows with no effort ladder at all.

Additive, not a replacement. The 3.6 generation vanished from CCA the moment
3.7 shipped, which is why RETIRED_FLASH_TIERS exists. 3.8 did not do that:
Google documents 3.7 Flash as remaining fully supported, and a live
fetchAvailableModels call returns 3.8, 3.7 and 3.6 together. So 3.7 stays
picker-visible and every retirement mapping is left where it is.

3.8 tiers ride SUFFIX wire ids, unlike the single -tiered id 3.7 uses, so it
is registered through ANTIGRAVITY_EFFORT_WIRE_MAP. Two consequences, each
proven against the backend rather than assumed:

- The suffix is the sole tier carrier. CCA accepts a -low wire id paired with
  a HIGH thinking level and returns 200, so a contradictory pair would run at
  an unknowable tier. ANTIGRAVITY_SUFFIX_TIER_MODELS suppresses the redundant
  level and makes static resolution byte-identical to the discovery path,
  which never emitted one. That divergence also swallowed clamped efforts:
  max/xhigh/ultra resolved differently before and after discovery ran.

- The Claude SDK identity paragraph 429s on 3.8 exactly as on 3.7. The strip
  guard was an equality check on one model id; with 3.8 becoming the default
  it would have returned RESOURCE_EXHAUSTED for every Claude-Agent-shaped
  request while looking like a quota problem. Membership is now canonicalized
  so raw suffix selectors from a partial ladder are covered too.

GEMINI_FLASH_WIRE_ID is renamed GEMINI_RETIRED_FLASH_TARGET_WIRE_ID: it holds
the 3.7 redirect target, and after 3.8 became current the old name pointed
readers at the wrong model.

Verification: bun run typecheck, plus focused
tests/google-antigravity-wire.test.ts, gemini-37-flash-migration.test.ts,
google-adapter.test.ts, provider-registry-parity.test.ts - 166 pass, 0 fail.

* feat(models): price and publish Gemini 3.8 Flash metadata

Adds the google/gemini-3.8-flash source record (regenerated, never hand-
edited) and the Antigravity + direct Google price rows.

The source record deliberately omits `cost`. Bundled generated metadata is
consulted before the expected-price overlay and returns status "verified", so
copying the adjacent 3.6 record - which does carry a cost block - would make
the Antigravity row unreachable and report CCA spend as a verified price.
Google publishes Developer API prices; it does not publish that Cloud Code
Assist charges them. The overlay is verified-derived for exactly that reason:
the number is proven, the claim that Antigravity bills it is inferred.

GEMINI_38_FLASH is its own constant despite matching 3.7 today, so a later
re-verification of one cannot silently move the other.

Nothing is retired here. Reconciliation refreshes capability records from the
registry, so an existing user picks up 3.8 on the next start, and a user who
deliberately chose 3.7 keeps it - Google still serves that model. The new
reconcile case asserts that preservation directly; the pre-existing 3.5 case
only exercised the opposite branch, where a retired default gets healed.

Verification: bun run typecheck, plus focused usage-cost, model-metadata-sync,
oauth-provider-reconcile and gemini-37-flash-migration - 137 pass, 0 fail.

* feat(models): carry Gemini 3.8 Flash to the remaining surfaces

Direct Google, the free-provider directory, the Gemini web-search sidecar
default, a preemptive Cursor seed, and the sidecar docs row.

The direct Google ladder omits `minimal` even though its 3.5/3.6/3.7
neighbours list it: Google documents `minimal` as a validation error for this
generation. defaultModel stays gemini-3.5-flash - adding a model elsewhere
must not silently move an existing API-key user's default.

The free-directory Gemini row gets a row-specific lastVerified rather than a
bumped shared constant, which would have stamped a 2026-09-03 check onto every
other provider row that nobody re-checked.

The Cursor seed follows the documented glm-5.3 precedent: Cursor has not
announced 3.8, and the static catalog is intersected with the live roster, so
the entry stays invisible until Cursor lists it.

Left alone deliberately: GEMINI_DIRECT_WIRE_RENAMES gains no 3.8 entry,
because no source anywhere proves a gemini-3.8-flash-tiered id exists, and the
providers.md rename example keeps naming 3.7 for the same reason.

Verification: bun run typecheck, plus 15 focused test files - 753 pass, 1 fail.
The failure is pre-existing and unrelated: 'Antigravity live model discovery
uses the CCA agent list' leaks a credential across files and fails on the
clean tree too, while passing when that file runs alone.

* fix(antigravity): strip the rejected paragraph by routed generation, not selector

The maintainer review found a hole three audit rounds missed, and a live probe
confirmed it: a saved gemini-3.6-flash selection does not call 3.6. Rule 0
redirects it onto gemini-3.7-flash-tiered, which rejects the Claude Agent SDK
identity paragraph with a 429 that reads as quota exhaustion.

The guard keyed on the selector through canonicalAntigravityUsageModel, which
covers the collapsed base and the raw suffix rows. It cannot cover retired ids,
because those deliberately keep their OWN identity for usage accounting - that
is the rule protecting historical spend from being relabelled. Two individually
correct mechanisms combined into a gap, and every saved 3.6/3.5 config would
have kept 429ing after this rollout.

Judging the routed wire id closes it, and naming a wire spelling once now covers
every selector that can reach that generation rather than requiring the set to
enumerate selectors that redirect into it. The old test asserting a 3.6
selection KEEPS the paragraph was asserting the bug; it is replaced by one
proving the strip, plus a real control on claude-sonnet-4-6 - a model with no
recorded rejection, where the paragraph is literally true.

Also folds the review's second point: the direct google 3.7 row no longer
advertises `minimal`. Google documents it as a validation error for that
generation, which is the same evidence 3.8 relies on, and the line was already
being edited here. 3.5 and 3.6 keep theirs - their pages still list it.

Verification: bun run typecheck, plus 12 focused test files - 681 pass, 0 fail.

---------

Co-authored-by: jun <jun@lidge.dev>
…on (#3289)

* docs(devlog): plan the bug-labelled PR and issue drawdown campaign

Roadmap unit for the September bug drawdown on dev: 000_plan.md locks a
ten-work-phase map from live gh evidence, and 010-090 carry per-item root
cause, file:line fix maps, and RED-before-fix assertions sourced from six
parallel read-only investigators.

Four decade docs cover the open bug-labelled PRs as adoption phases with
immutable base/head anchors. Five cover the bug-labelled issues, three of
which terminate as NEEDS_HUMAN with the ruled-out causes recorded rather
than a speculative patch.

* fix(responses): stop the background write storm on responses-state.json

A completed response mutates the continuation cache and calls schedulePersist,
and the process-level timer already coalesces those triggers. Under concurrent
completions, though, the revision kept changing during the async write, so the
bounded-retry loop rewrote the whole snapshot up to four times per background
tick. The existing test codified that as the contract.

Ordinary background persistence now gets a single attempt. When the snapshot is
unstable it schedules the existing delayed follow-up instead of rewriting
immediately, so an unstable revision costs one extra debounce interval rather
than three extra full atomic rewrites. Graceful shutdown keeps the bounded retry:
that path drains requests first and has to land.

Closes #3141

---------

Co-authored-by: jun <jun@lidge.dev>
* docs(devlog): plan the bug-labelled PR and issue drawdown campaign

Roadmap unit for the September bug drawdown on dev: 000_plan.md locks a
ten-work-phase map from live gh evidence, and 010-090 carry per-item root
cause, file:line fix maps, and RED-before-fix assertions sourced from six
parallel read-only investigators.

Four decade docs cover the open bug-labelled PRs as adoption phases with
immutable base/head anchors. Five cover the bug-labelled issues, three of
which terminate as NEEDS_HUMAN with the ruled-out causes recorded rather
than a speculative patch.

* fix(responses): stop the background write storm on responses-state.json

A completed response mutates the continuation cache and calls schedulePersist,
and the process-level timer already coalesces those triggers. Under concurrent
completions, though, the revision kept changing during the async write, so the
bounded-retry loop rewrote the whole snapshot up to four times per background
tick. The existing test codified that as the contract.

Ordinary background persistence now gets a single attempt. When the snapshot is
unstable it schedules the existing delayed follow-up instead of rewriting
immediately, so an unstable revision costs one extra debounce interval rather
than three extra full atomic rewrites. Graceful shutdown keeps the bounded retry:
that path drains requests first and has to land.

Closes #3141

* fix(gui): stop the log panel from jittering as rows scroll in

The virtualized log table measured rows dynamically from a 44px estimate while
its multiline cells are roughly twice that, and the table itself was on
automatic layout. Every changed mounted-row subset therefore recomputed
intrinsic column widths, model wrapping changed row heights, and that fed
another virtualizer measurement.

Fixed layout with an explicit ten-column schema removes the feedback loop:
column widths no longer depend on which rows happen to be mounted. The estimate
moves to the observed mean, getItemKey keys measurements to the request rather
than the index so a prepend does not shift them, and the wrapper disables scroll
anchoring and reserves the scrollbar gutter.

Closes #3152

---------

Co-authored-by: jun <jun@lidge.dev>
* docs(devlog): Gemini 3.8 Flash rollout roadmap (260903_gemini_38_rollout wp0)

Diff-level roadmap for rolling Gemini 3.8 Flash across every surface that
currently names 3.6/3.7.

Two findings shape the plan, both first-hand rather than inherited from the
3.7 unit:

- Google documents 3.7 Flash as "remains fully supported", and a live CCA
  fetchAvailableModels call returns 3.5, 3.6, 3.7 and 3.8 together. So this
  is an ADDITIVE rollout with a default move, not the hard replacement the
  3.6 to 3.7 migration performed.
- CCA publishes gemini-3.8-flash-{low,medium,high} and no -tiered row, so 3.8
  is structurally 3.6-shaped: it belongs in ANTIGRAVITY_EFFORT_WIRE_MAP, not
  in the single-wire thinkingLevel map 3.7 uses.

Three adversarial audit rounds (independent gpt-5.6-sol reviewer at high
effort) took this from FAIL to PASS. The two most valuable findings were
confirmed against the live backend: the Claude SDK identity paragraph 429s on
3.8 exactly as on 3.7, so that strip guard must widen before 3.8 becomes the
default; and static vs discovered effort resolution returned different request
bodies for the same input.

Docs only. No runtime change in this commit.

* feat(antigravity): expose Gemini 3.8 Flash with its suffix effort ladder

Google shipped Gemini 3.8 Flash on 2026-09-02 and CCA already returns it
ranked first in the Recommended sort, but discovery published it as three
uncollapsed rows with no effort ladder at all.

Additive, not a replacement. The 3.6 generation vanished from CCA the moment
3.7 shipped, which is why RETIRED_FLASH_TIERS exists. 3.8 did not do that:
Google documents 3.7 Flash as remaining fully supported, and a live
fetchAvailableModels call returns 3.8, 3.7 and 3.6 together. So 3.7 stays
picker-visible and every retirement mapping is left where it is.

3.8 tiers ride SUFFIX wire ids, unlike the single -tiered id 3.7 uses, so it
is registered through ANTIGRAVITY_EFFORT_WIRE_MAP. Two consequences, each
proven against the backend rather than assumed:

- The suffix is the sole tier carrier. CCA accepts a -low wire id paired with
  a HIGH thinking level and returns 200, so a contradictory pair would run at
  an unknowable tier. ANTIGRAVITY_SUFFIX_TIER_MODELS suppresses the redundant
  level and makes static resolution byte-identical to the discovery path,
  which never emitted one. That divergence also swallowed clamped efforts:
  max/xhigh/ultra resolved differently before and after discovery ran.

- The Claude SDK identity paragraph 429s on 3.8 exactly as on 3.7. The strip
  guard was an equality check on one model id; with 3.8 becoming the default
  it would have returned RESOURCE_EXHAUSTED for every Claude-Agent-shaped
  request while looking like a quota problem. Membership is now canonicalized
  so raw suffix selectors from a partial ladder are covered too.

GEMINI_FLASH_WIRE_ID is renamed GEMINI_RETIRED_FLASH_TARGET_WIRE_ID: it holds
the 3.7 redirect target, and after 3.8 became current the old name pointed
readers at the wrong model.

Verification: bun run typecheck, plus focused
tests/google-antigravity-wire.test.ts, gemini-37-flash-migration.test.ts,
google-adapter.test.ts, provider-registry-parity.test.ts - 166 pass, 0 fail.

* feat(models): price and publish Gemini 3.8 Flash metadata

Adds the google/gemini-3.8-flash source record (regenerated, never hand-
edited) and the Antigravity + direct Google price rows.

The source record deliberately omits `cost`. Bundled generated metadata is
consulted before the expected-price overlay and returns status "verified", so
copying the adjacent 3.6 record - which does carry a cost block - would make
the Antigravity row unreachable and report CCA spend as a verified price.
Google publishes Developer API prices; it does not publish that Cloud Code
Assist charges them. The overlay is verified-derived for exactly that reason:
the number is proven, the claim that Antigravity bills it is inferred.

GEMINI_38_FLASH is its own constant despite matching 3.7 today, so a later
re-verification of one cannot silently move the other.

Nothing is retired here. Reconciliation refreshes capability records from the
registry, so an existing user picks up 3.8 on the next start, and a user who
deliberately chose 3.7 keeps it - Google still serves that model. The new
reconcile case asserts that preservation directly; the pre-existing 3.5 case
only exercised the opposite branch, where a retired default gets healed.

Verification: bun run typecheck, plus focused usage-cost, model-metadata-sync,
oauth-provider-reconcile and gemini-37-flash-migration - 137 pass, 0 fail.

* feat(models): carry Gemini 3.8 Flash to the remaining surfaces

Direct Google, the free-provider directory, the Gemini web-search sidecar
default, a preemptive Cursor seed, and the sidecar docs row.

The direct Google ladder omits `minimal` even though its 3.5/3.6/3.7
neighbours list it: Google documents `minimal` as a validation error for this
generation. defaultModel stays gemini-3.5-flash - adding a model elsewhere
must not silently move an existing API-key user's default.

The free-directory Gemini row gets a row-specific lastVerified rather than a
bumped shared constant, which would have stamped a 2026-09-03 check onto every
other provider row that nobody re-checked.

The Cursor seed follows the documented glm-5.3 precedent: Cursor has not
announced 3.8, and the static catalog is intersected with the live roster, so
the entry stays invisible until Cursor lists it.

Left alone deliberately: GEMINI_DIRECT_WIRE_RENAMES gains no 3.8 entry,
because no source anywhere proves a gemini-3.8-flash-tiered id exists, and the
providers.md rename example keeps naming 3.7 for the same reason.

Verification: bun run typecheck, plus 15 focused test files - 753 pass, 1 fail.
The failure is pre-existing and unrelated: 'Antigravity live model discovery
uses the CCA agent list' leaks a credential across files and fails on the
clean tree too, while passing when that file runs alone.

* fix(antigravity): strip the rejected paragraph by routed generation, not selector

The maintainer review found a hole three audit rounds missed, and a live probe
confirmed it: a saved gemini-3.6-flash selection does not call 3.6. Rule 0
redirects it onto gemini-3.7-flash-tiered, which rejects the Claude Agent SDK
identity paragraph with a 429 that reads as quota exhaustion.

The guard keyed on the selector through canonicalAntigravityUsageModel, which
covers the collapsed base and the raw suffix rows. It cannot cover retired ids,
because those deliberately keep their OWN identity for usage accounting - that
is the rule protecting historical spend from being relabelled. Two individually
correct mechanisms combined into a gap, and every saved 3.6/3.5 config would
have kept 429ing after this rollout.

Judging the routed wire id closes it, and naming a wire spelling once now covers
every selector that can reach that generation rather than requiring the set to
enumerate selectors that redirect into it. The old test asserting a 3.6
selection KEEPS the paragraph was asserting the bug; it is replaced by one
proving the strip, plus a real control on claude-sonnet-4-6 - a model with no
recorded rejection, where the paragraph is literally true.

Also folds the review's second point: the direct google 3.7 row no longer
advertises `minimal`. Google documents it as a validation error for that
generation, which is the same evidence 3.8 relies on, and the line was already
being edited here. 3.5 and 3.6 keep theirs - their pages still list it.

Verification: bun run typecheck, plus 12 focused test files - 681 pass, 0 fail.

* docs(devlog): Gemini 3.8 Flash rollout closeout

Records the merge (3d3c4fe), the 25/25 CI rollup on the exact head, and the
audit history - including the maintainer-review finding that three adversarial
plan rounds missed because it lived in the interaction between two individually
correct mechanisms.

---------

Co-authored-by: jun <jun@lidge.dev>
…ombo 503 (#3294)

Request-rate 429s without Retry-After no longer inherit the 60s quota cooldown.
Combo unavailable responses advertise the soonest remaining target cooldown.
* fix(usage): aggregate complete ledger incrementally

* test(usage): bind overlay retry assertion to scan version

---------

Co-authored-by: jun <jun@lidge.dev>
* fix(providers): save the dashboard provider editor atomically

The dashboard's JSON editor serialized the redacted config DTO and PUT it to
/api/config, which the server rejects on purpose, so Save always failed with
"Full config PUT is disabled."

Fanning the edit out to per-provider POST/PATCH/DELETE would have fixed the
error message and introduced a worse bug: each of those persists
independently, so a mid-sequence failure leaves half the edit on disk, and
every field absent from the public DTO is lost on the way back.

So the write stays server-side. PUT /api/providers takes { baseline, next }:
the GUI sends only what it can see, the server compares the baseline against
the current public projection, merges next into freshly read persisted
providers while keeping api keys, pools, headers and other private fields,
validates everything, and commits once. A stale baseline is a 409 rather than
a silent overwrite, and derived markers like hasApiKey are rejected instead of
being written back as data. The /api/config 405 is unchanged.

Closes #3280

* fix(providers): round-trip complete safe editor config

* fix(providers): keep runtime metadata out of batch edits

* fix(providers): restore the safeConfigDTO runtime-metadata contract

The previous commit reclassified modelMaxInputTokens as runtime so it could
not be editor write authority. That was right, but the same commit also
relaxed the existing safeConfigDTO assertion so the field could appear in the
public DTO.

That assertion was not incidental: dev already listed modelMaxInputTokens
among the values safeConfigDTO must never serialize. Weakening a security
contract to fit a new implementation is backwards, so the test is restored
verbatim and the implementation now satisfies it as written.

---------

Co-authored-by: jun <jun@lidge.dev>
Mirrors #3106 (health) for the ocx claude pre-spawn probe; budget origin #764 (SERVICE_STOP_LIVENESS).

Co-authored-by: wj <wj@nas-backup>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts:
#	src/combos/resolve.ts
#	tests/combos.test.ts
Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: jun <jun@lidge.dev>
Records the sixteen merges with their ancestor-proved shas, the three issues
that ended NEEDS_HUMAN and why a diff would have been a guess, and the four
things the process caught that a green build would not: a plan audit that
failed four times over two factual errors in my own writeup, an allowlist that
passed every test and rejected a real config in the browser, CI catching two
contract breaks in my own change plus a subagent relaxing an existing security
assertion to fit its implementation, and a contribution that silently reverted
an earlier fix.

Also lists the three macOS timing flakies that recurred across this campaign,
so the next person does not re-investigate them.

Co-authored-by: jun <jun@lidge.dev>
lidge-jun and others added 8 commits September 3, 2026 10:16
Meta shipped Muse Spark 1.3 on 2026-09-02. Command Code publishes
meta/muse-spark-1.3 and meta/muse-spark-1.3-contributor alongside the 1.2
pair, and OpenCode Zen serves muse-spark-1.3-contributor (Go) and
muse-spark-1.3-contributor-free (Zen) on the same wire as their 1.2
siblings.

Additive, not a replacement: every 1.2 and 1.1 id stays exactly where it
is. 1.3 inherits the 1.2 spec on every surface that already named 1.2,
because it is the same-shaped successor on the same gateway.

Four surfaces move together, and three of them would have failed silently
had 1.3 only been added to the first:

- Effort ladder (low/medium/high/xhigh/max) for both 1.3 ids, so a client
  that sends an effort gets it forwarded rather than stripped.
- Command Code image models, so the catalog does not advertise a
  multimodal model as text-only.
- OpenCode Go registry: Responses wire default, the 1M context window, and
  text+image. Zen Go publishes no modality or window metadata, so without
  these declarations 1.3 regresses to the 128k default and the Codex app
  blocks image attachments client-side — the exact pair of defects the 1.2
  entries were added to fix.
- The Muse web_search guard was an equality check on the 1.2 id. Selecting
  1.3 would have let Codex's search_content_types field reach a gateway
  that 400s it. It is now a membership set.

Verification: focused bun test on the four Muse/Command Code suites (55
pass) plus bun run typecheck. The repository-wide local suite was not run
at the user's instruction; CI is the full gate.

Co-authored-by: jun <jun@lidge.dev>
…and gate it (#3318)

CREDITS.md records the landings whose original author is named in prose and
nowhere a tool can read: 27 commits across 26 contributor pull requests, found
by scanning dev commit trailers against maintainer closure comments on the 119
closed-unmerged external PRs since #2400. Rows are graded by what the
maintainer actually wrote - carried work where code, design, or tests shipped,
report and diagnosis where the fix exists because of the report and the
branch's approach was explicitly not the vehicle.

The trailers cannot be added retroactively: every affected commit but one sits
inside a published tag, and dev/main/preview each block force-pushes.
MAINTAINERS.md already says authorship in git history is not rewritten, so the
repair goes forward into a file, into the release notes, and into a gate.

missing_coauthor_credit fails a pull request whose own text says it
reimplements, supersedes, carries, or rebases another author's pull request
until a Co-authored-by trailer names that author. Fail-open on an unresolved
lookup, capped at five per run, matched on git identity rather than login, and
cleared by attribution-approved.

Verification: node --test .github/scripts/*.test.cjs 541 pass 0 fail;
bun test tests/ci-workflows.test.ts 135 pass; bun run test:changed 140 pass;
privacy:scan and typecheck clean.
Records the merge 7a529a2, the six release bodies credited afterwards, and the four defects caught by a check rather than by reading. Docs only.
* docs(devlog): Muse Spark direct-provider roadmap and plan-credential verdict

wp0 of 260903_muse_spark_plan_oauth. Docs only; no runtime change.

Two questions, two different answers.

Can opencodex reach Muse Spark on Meta's own endpoint? Yes, and 010 is a
diff-level plan for it: an OpenAI-compatible Responses provider at
https://api.meta.ai/v1 built entirely from published spec. No API key was
issued; the only contact with the endpoint was an unauthenticated GET that
returned 401 invalid_api_key, which proves the host routes /v1 and answers
in OpenAI error shape.

Can a Muse Code subscription drive it? No, and that answer changed twice.

The first draft said no third-party OAuth existed, sourced from a docs search
returning 'No matching results' and Authentication's flat 'every request needs
an API key'. Installing the CLI disproved it in one command: muse login opens
a browser device-approval flow at auth.meta.com/oauth/device that Meta does
not document.

Finding it did not make it usable. The plan then became: complete a login,
extract the credential, fire it at api.meta.ai, ship if it returns 200. The
A-gate reviewer named the flaw - that experiment tests whether enforcement is
absent, not whether use is permitted, and Meta answered the second question in
writing before anyone asked ('This credential is for use with Muse Code only').
An unenforced restriction is still a restriction. So wp2 closes NOOP: no
credential extracted, no login completed, no API test, and none exists on this
machine.

The user-visible half ships in wp1's provider note, which states outright that
a Muse Code subscription does not apply here and every call is metered.

Four adversarial audit rounds against one independent gpt-5.6-sol reviewer
(FAIL, FAIL, FAIL, GO-WITH-FIXES) found what reading alone did not. Two were
invisible from the vendor docs and only existed in this repository:

- Provider id 'meta' would have hijacked 'meta/muse-spark-1.3', a live Command
  Code selector merged last week. router.ts:676 resolves the prefix against
  configured providers, so a working model reference would have silently
  changed destination. Now 'meta-model', with a routing regression required.
- The advertised 'minimal' effort would have been rewritten to 'low' by
  reasoning-effort.ts:171. The registry array looked correct the whole time,
  which is why the tests now assert the built Responses body.

Also folded: liveModels false (no authenticated roster was ever observed),
preserveCustomDestination, decided price overlays with full Cost4, the
order-sensitive parity roster, and the stack collapsed to a single PR.

* feat(providers): add the direct Meta Model API provider

Muse Spark reached opencodex through resellers only (command-code,
opencode-go). This adds Meta's own endpoint as meta-model, an
openai-responses key provider on https://api.meta.ai/v1.

Built entirely from published spec. No API key was issued: the only contact
with the endpoint was an unauthenticated GET /v1/models returning 401
invalid_api_key, which proves the host routes /v1 and answers in OpenAI error
shape. Every value traces to a vendor page recorded in the plan's claim
ledger, and anything Meta does not publish is absent rather than guessed --
notably max output tokens, whose only apparent value (131072) lives inside a
third-party config sample while the protocol docs call the real limit
model-dependent.

Four decisions are not obvious from the vendor docs, and three of them come
from this repository rather than from Meta.

The id is meta-model, not meta. router.ts resolves a <provider>/<model>
prefix against configured providers first, so a provider named meta would
have captured meta/muse-spark-1.3 -- a live Command Code selector merged last
week -- and silently redirected a working model reference to a different
vendor and a different bill. It would also have derived META_API_KEY, which
is the Muse Code CLI's variable, not this API's MODEL_API_KEY.

The effort ladder carries an identity wire map. Meta publishes
minimal/low/medium/high/xhigh and excludes none for Muse Spark (HTTP 400);
max and ultra are absent from its list entirely. But reasoning-effort.ts
rewrites minimal to low unless a model-scoped map says otherwise, so the
picker would have advertised an effort the wire never sent. The registry
array looked correct throughout, which is why the test asserts the built
Responses body instead.

liveModels is false. No authenticated /v1/models payload was ever observed,
and Meta serves image and voice families on this same base URL, so discovery
would publish rows this Responses-agent provider cannot drive.

preserveCustomDestination is set, so a user who already owns a custom
provider under this id keeps their destination and their key.

The note states plainly that a Muse Code subscription does NOT apply here.
Meta scopes that credential to its own CLI and meters every other key per
token, so a user assuming their plan covers this would be billed
pay-as-you-go. The Contributor tier's ~92% discount is disclosed as what it
is: Meta trains on those prompts.

Also: two price overlays (meta-model has no jawcode alias, so without them
the cost column renders nothing), the order-sensitive parity roster, and the
docs-site provider tables.

Plan, claim ledger, and the four-round audit trail:
devlog/_plan/260903_muse_spark_plan_oauth/.

* fix(providers): name the env var opencodex actually reads for Meta

CodeRabbit review findings on #3321. Two were user-facing defects.

The docs told a user to set MODEL_API_KEY, which is Meta's own name for the
variable. opencodex derives the env var from the provider id, so it reads
META_MODEL_API_KEY and would never have seen the key. Following the docs
verbatim produced a provider that silently failed to authenticate. Both the
provider note and the docs-site section now give Meta's name and the one this
proxy reads.

The Contributor tier was described as '~92% cheaper', which is true only of
input. The published prices are about 92% off input, 95% off output, and 99%
off cached input, and a single tier-wide figure understates the saving while
sounding like a precise claim. All three are stated now.

Three documentation findings, all accepted:

- 002 asserted that the credential muse login stores IS the subscription
  credential. No login completed, so that was never measured. It is now stated
  as inferred from Meta's description of CLI onboarding, with the reason the
  conclusion holds either way: muse login is the Muse Code CLI's own sign-in,
  so whatever it yields has no stronger claim to third-party use.
- The verification gate differed across three plan documents. 030 now holds one
  canonical gate and the others reference it, including the docs-site
  frozen-lockfile install and build that docs-site/AGENTS.md requires whenever
  that directory is touched. Verified: 417 pages built.
- A test-count sentence said six where the block has seven.

* fix(docs): narrow the reseller claim for Muse Spark

CodeRabbit second-pass finding on #3321, and it is right: opencode-go serves
only muse-spark-1.3-contributor, not the standard tier. Saying 'the same
models are also reachable' would send a user to a preset that does not carry
the model they were reading about.

Both the provider note and the docs-site section now name which reseller
carries which tier: command-code has both, opencode-go has Contributor only.

Also corrects 'seven assertions' to 'seven tests' in the plan doc; the block
has seven test cases and more than seven expect calls.

---------

Co-authored-by: jun <jun@lidge.dev>
…3330)

#3222 seeded a display label for every Cursor row, so the Codex picker lost the
cursor/ prefix: cursor/kimi-k3 rendered as "Kimi K3", indistinguishable from
the same model under another provider. Publish only the labels that carry
Cursor's own product name (Cursor Grok 4.5/4.6); every other row keeps the
routed cursor/<id> slug like its siblings from other providers.

Operator modelDisplayNames still win; enrich remains per-model fill-only.

Co-authored-by: jun <jun@lidge.dev>
…#3337)

* docs(devlog): plan the meta-muse OAuth provider and its ToS warning

wp4 plan plus the research it rests on. Five audit rounds; the reviewer failed it four times.

* feat(oauth): import the Muse Code CLI credential behind a ToS warning

Adds `meta-muse`, an OAuth provider that reuses the API key the Muse Code
CLI already holds, for operators who signed that CLI in and would rather not
provision a second key.

This ships because the repository owner authorized it for his own account.
An earlier phase closed the same idea as a NOOP, and that reasoning stands:
proving a credential works is not the same as being allowed to use it, so an
agent must not spend a user's ToS risk on its own initiative. A user spending
his own deliberately is a different act, and the repository already models it
- anthropic and google-antigravity sit in the same HIGH_RISK map.

Two measurements shaped the design. The credential file at
~/.config/muse/auth.json holds no secret; it is a pointer to a macOS Keychain
item. That item carries both an access_token and an api_key, and only the
api_key authenticates: the OAuth access token returns 401 invalid_api_key on
/v1/models while the sibling key returns 200. So this is a static-key
credential with nothing to refresh, the shape command-code already uses.

Import-only, and macOS-only. `muse login` has no non-interactive mode, so a
spawned child could outlive cancellation, and polling for the pointer file is
satisfied instantly by the one already on disk - which would reimport the OLD
account on a force-login. When no credential is present the provider says what
to run instead of running it.

The warning reaches both surfaces, which took two fixes:

- The GUI map alone was not enough. Reauthentication called loginOAuth
  directly, so a user who had already logged in could refresh a high-risk
  credential without ever seeing the modal. onReauth now routes through the
  warning-aware path, carrying accountId so acknowledgement continues the same
  operation rather than a plain login against the active account.
- login-cli.ts never reads the registry note, so `ocx login meta-muse` had no
  warning at all. loginMetaMuse emits it through ctrl.onProgress before it
  touches the pointer or the Keychain.

The disclosures say what is actually known. Meta scopes this credential to its
own CLI and how these calls settle is not observable from the API, so the note
says treat every call as billable rather than asserting pay-as-you-go as fact.
It also states that the key is copied into OpenCodex's auth store, because it
is - runLogin persists it like every other OAuth credential.

Also: two price overlays (overlays resolve by exact provider id, so a provider
whose warning is 'treat every call as billable' would otherwise report no
cost), a privacy-scan detector for the measured LLM|<digits>|<tail> key shape
exercised through a new exported scanText seam, and a GUI test that asserts
login, add-account and reauth each reach login zero times before
acknowledgement and once after.

supportsPerAccountQuota stays false, with a test. That predicate gates
fetchAccountQuota, whose fallback sends any non-Kiro/non-Antigravity bearer to
Anthropic's usage endpoint - flipping it without a dedicated branch would ship
a Meta key to Anthropic. Quota is deferred to wp5: Meta does report
subscription windows, but only as an SSE event on streaming turns, which needs
a passive cache rather than a probe.

Plan and five-round audit trail: devlog/_plan/260903_muse_spark_plan_oauth/.

* fix(test): scale the CL-07 producer isolation budget under load

tests/lab-fabric-task.test.ts failed intermittently on the macOS CI lane and
in local full-suite runs, with four or five failures in the same describe
block. It read as a flake. It is not one.

The tests shorten the fabric producer's isolation budget from the product's
30s/5s to 2000ms/750ms so a hung producer fails in about a second instead of
stalling the suite. That budget starts counting when the parent spawns a Bun
CHILD process. Spawning one while the rest of the suite saturates the CPU can
take longer than 750ms by itself, so the child is killed for inactivity before
it runs a line - and the assertion then sees whatever the harness makes of a
killed producer: inactivity_timeout where it expected sandbox_violation, or
blocked where it expected pass.

That is deterministic under contention, not random. Eight parallel runs of the
file reproduced five failures each, at a near-identical ~760ms, while a single
run passes 49/49. It only looked flaky because it needs a busy machine, which
is also why the same four tests passed on one CI run and failed on another.

The fix mirrors the watchdogMs helper that already exists for the adjacent
problem. watchdogMs bounds how long a TEST may run; isolationBudgetMs scales a
PRODUCT budget a test deliberately shortened, with a floor that only applies
under load (CI, or a full-suite lane) and leaves a lone local run untouched.

The total budget is now a multiple of the inactivity budget rather than a
fixed 2000ms. fabricActivityPatchExecutor sleeps 40% of the inactivity budget
three times to prove activity resets the deadline, so it needs ~1.2x
inactivity to finish; pinning the total while inactivity scaled up starved
exactly the test that exercises the scaling.

scripts/test.ts marks its spawned lanes with OCX_TEST_FULL_SUITE=1, since a
lane running many files in parallel is the same contention as CI.

* fix(test): reauth reaches login through the ToS gate, not around it

tests/provider-workspace-auth.test.ts pinned the exact call
`loginOAuth(provider, true, accountId)` in the onReauth handler. That
assertion was correct when it was written: its point is that
re-authentication actually reaches login rather than dead-ending.

It now conflicts with the fix in the previous commit. Reauth was calling
loginOAuth directly, which meant a user who had already logged in could
refresh a high-risk credential without ever seeing the Terms-of-Service
warning - the map gated the first login and nothing after it.

The assertion is updated rather than the code reverted, because the seam it
guards is unchanged: requestLoginOAuth forwards the same
(provider, addAccount, accountId) triple, and the continuation now carries
accountId so acknowledging the warning resumes the same operation instead of
a plain login against the active account. Both halves are asserted.

Independently, CI confirms the CL-07 isolation-budget fix worked: all 49
tests in that file passed on the macOS lane, including the four that had been
failing, with the activity test taking 6.07s under the scaled budget where
the old 750ms budget killed its producer mid-spawn.

* fix(oauth): redact scanned secrets and bound the Keychain read

Both findings from CodeRabbit, and both were right.

The privacy scanner printed finding.value to stderr on failure. For a home
path or an email that is the context a reviewer needs. For a bearer token or
an API key it means the scan that exists to keep a secret out of a readable
artifact copies it into CI logs, which are more widely readable than the diff
it was blocking. Credential-shaped kinds now report location and kind only.

defaultReadKeychain waited on `security find-generic-password` with no
deadline. That call can raise an interactive Keychain approval prompt, and on
a headless or locked machine nobody answers it - so the login would hang
before the 10s validation timeout was even created. It now races a 5s deadline
combined with the caller's abort signal, and kills the child in a finally
block so a prompt still on screen cannot outlive the race.

Two tests: a blocked read fails with a bounded message rather than hanging,
and the caller's signal actually reaches the reader.

---------

Co-authored-by: jun <jun@lidge.dev>
* docs(devlog): Muse release-train roadmap and audit round 1

Plans the v2.41.0 release train: a risk-classed regression review of the 36
commits on dev ahead of main, a Meta provider mark, and the preview/main
publish path.

The manual release path exists because the user forbade the local full suite
for this unit, and scripts/release.ts runs it in preflight. Audit round 1
established that a hand-rolled path is strictly weaker than the helper unless
it also proves the version unused before bumping, uses push-event CI on the
exact 40-char SHA, and re-reads the live remote head before dispatch; all three
are now in 030/040.

005 records the audit synthesis, including the one finding rebutted: the
Terms-of-Service acknowledgement for high-risk OAuth providers is enforced in
the browser rather than at /api/oauth/login. That is real, but it predates this
work and applies identically to anthropic and google-antigravity, so it is
recorded as a follow-up in 050 rather than folded into a release cycle.

* feat(gui): give both Meta providers their brand mark

meta-model and meta-muse landed today with no icon alias and no display name,
so the dashboard drew them as an initial tile with a raw config id underneath.
Every other first-class provider carries both.

meta.svg is the aria-label="Meta symbol" inline SVG that dev.meta.ai renders in
its own navigation header, read through a signed-in session. Meta publishes no
square vector at the conventional paths -- favicon.svg, icon.svg and logo.svg
all 404 on that host, and the declared icon is a 32x32 .ico -- so the rendered
header mark is the first-party vector, taken from the developer console these
two providers actually belong to.

Path data and gradient stops are verbatim. React's generated gradient ids
(_r_d_, _r_e_, _r_f_) are renamed to meta-mark-a/-b/-c because a generated id
collides when several marks are inlined into one document, which is the same
reason minimax.svg renamed its unnamed-gradient id; the presentational
height/width/role/aria-label give way to the viewBox, and xmlns is added so the
file stands alone.

One brand, two credentials: meta-model is Meta's own pay-as-you-go Model API,
meta-muse imports the Muse Code CLI credential. Sharing one asset is the same
shape as the three Alibaba plan ids. The mark is not masked -- three linear
gradients in Meta brand blue, and masking flattens a gradient to one ink.

The generic wiring check only fires when an asset named after the provider id
is already committed, and neither id is "meta", so a dropped alias row would
restore the fallback tile silently. Both are pinned explicitly instead.

* docs(devlog): screenshot of the Meta provider mark

The provider rail before and after, on both tile surfaces. The fallback tile
and the new mark are the same 19px box, which is the only way to see that a
gradient mark actually reads at that size on #303030 and #f4f4f4 both.

* docs(devlog): per-commit regression review of the 36-commit release delta

Every commit dev carries ahead of main, risk-classed and evidenced. Four
commits get R3 treatment: the Muse credential import, the reusable-workflow
permission grant, the pull_request_target attribution gate, and the provider
editor's field-admission policy. All four are clean on a line-level read.

1387 focused assertions across 38 test files, zero failures, plus typecheck and
privacy:scan at the dev head. The full suite was not run, at the maintainer's
instruction for this unit.

One accepted residual, not a blocker: the ToS acknowledgement for high-risk
OAuth providers is enforced in the browser rather than at /api/oauth/login. It
predates this delta and applies identically to anthropic and
google-antigravity, so publishing 2.41.0 changes nobody's exposure.

* docs(devlog): fold the CodeRabbit review into the release plan

Six findings, all correct.

The ledger now carries one row per SHA. The grouped Cursor and combos rows hid
exactly what a per-commit ledger exists to show: 2ab9d94 touches four request
paths while its neighbours touch one file each, and that is not visible from a
row reading "clean, covered by the batch". R1 and R0 become tables for the same
reason.

The credential format example moves out of its table cell. Two pipe characters
inside a five-column row are delimiters to a Markdown parser, so the verdict
column was not where it claimed to be.

The version preflights become blocking commands rather than commands that
retrieve data. The channel-forward check has no automated equivalent: release.yml
compares only for exact-version duplication, so nothing in CI stops a dist-tag
being moved backwards.

The ancestry proof fetches main first. git ls-remote reads the remote without
updating origin/main, so the merge-base could have evaluated stale history.

The artifact check covers meta-model, meta-muse and meta.svg rather than one of
the three, and the ToS verdict rule is now stated as a rule instead of leaving
a later reader to infer why one bypass was accepted.

---------

Co-authored-by: jun <jun@lidge.dev>
Promotes the reviewed dev head (81a1fc1) onto preview and moves the channel
to 2.41.0-preview.20260903.

The delta since v2.40.0 is 37 commits, reviewed per-commit in
devlog/_plan/260903_muse_release_train/011_review_ledger.md: four R3
credential/workflow-permission changes read line by line, no blockers, one
accepted pre-existing residual recorded in 050_followups.md.

Preflight before this bump: 2.41.0-preview.20260903 unpublished on npm, no
v2.41.0* tag, no GitHub release, and it moves preview forward from
2.40.0-preview.20260902.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 3, 2026 08:34
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot changed the title release: v2.41.0-preview.20260903 [WRONG BRANCH] release: v2.41.0-preview.20260903 Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • wrong target branch (preview); retarget to dev.

What to do

  • Retarget this PR to dev — all contributions go to dev.

Its title has been prefixed with [WRONG BRANCH].
This pull request was already a draft. Its draft status will be preserved after every issue above is resolved.

@github-actions
github-actions Bot marked this pull request as draft September 3, 2026 08:34
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: bcf5f143-eb1c-49e5-86af-ee43bc9d3047

📥 Commits

Reviewing files that changed from the base of the PR and between 49812c9 and d604349.

⛔ Files ignored due to path filters (5)
  • devlog/_plan/260902_cursor_bundle_effort_table/041_wp4_screenshot.png is excluded by !**/*.png
  • devlog/_plan/260903_muse_release_train/021_meta_mark_screenshot.png is excluded by !**/*.png
  • gui/public/provider-icons/meta.svg is excluded by !**/*.svg
  • src/generated/model-metadata.ts is excluded by !**/generated/**
  • tests/fixtures/cursor-agent-exec-effort-table.min.js is excluded by !**/*.min.js
📒 Files selected for processing (240)
  • .github/scripts/pr-carry-attribution.cjs
  • .github/scripts/pr-carry-attribution.test.cjs
  • .github/scripts/pr-hygiene.cjs
  • .github/scripts/pr-referenced-authors.cjs
  • .github/scripts/pr-referenced-authors.test.cjs
  • .github/workflows/enforce-pr-target.yml
  • .github/workflows/pr-hygiene.yml
  • .github/workflows/release.yml
  • .github/workflows/service-lifecycle.yml
  • AGENTS.md
  • CONTRIBUTING.md
  • CREDITS.md
  • README.md
  • devlog/_plan/260902_cursor_bundle_effort_table/000_research.md
  • devlog/_plan/260902_cursor_bundle_effort_table/001_bundle_protocol.md
  • devlog/_plan/260902_cursor_bundle_effort_table/005_audit_round1.md
  • devlog/_plan/260902_cursor_bundle_effort_table/010_wp1_bundle_effort_table.md
  • devlog/_plan/260902_cursor_bundle_effort_table/020_wp2_models_max_output.md
  • devlog/_plan/260902_cursor_bundle_effort_table/030_wp3_effort_variant_rows.md
  • devlog/_plan/260902_cursor_bundle_effort_table/040_wp4_gui_provenance_hint.md
  • devlog/_plan/260902_cursor_bundle_effort_table/050_wp5_claude_id_normalizer.md
  • devlog/_plan/260902_cursor_bundle_effort_table/060_wp6_guide.md
  • devlog/_plan/260902_windows_ci_release/070_outcome.md
  • devlog/_plan/260902_windows_ci_release/080_release_automation_followups.md
  • devlog/_plan/260903_bug_drawdown_bcda/000_plan.md
  • devlog/_plan/260903_bug_drawdown_bcda/010_phase1.md
  • devlog/_plan/260903_bug_drawdown_bcda/020_phase2.md
  • devlog/_plan/260903_bug_drawdown_bcda/030_phase3.md
  • devlog/_plan/260903_bug_drawdown_bcda/040_phase4.md
  • devlog/_plan/260903_bug_drawdown_bcda/050_phase5.md
  • devlog/_plan/260903_bug_drawdown_bcda/060_phase6.md
  • devlog/_plan/260903_bug_drawdown_bcda/070_phase7.md
  • devlog/_plan/260903_bug_drawdown_bcda/080_phase8.md
  • devlog/_plan/260903_bug_drawdown_bcda/090_phase9.md
  • devlog/_plan/260903_bug_drawdown_bcda/100_closeout.md
  • devlog/_plan/260903_contributor_credit_restoration/000_plan.md
  • devlog/_plan/260903_contributor_credit_restoration/010_credits_file.md
  • devlog/_plan/260903_contributor_credit_restoration/020_hygiene_gate.md
  • devlog/_plan/260903_contributor_credit_restoration/030_release_notes.md
  • devlog/_plan/260903_contributor_credit_restoration/100_closeout.md
  • devlog/_plan/260903_gemini_38_rollout/000_plan.md
  • devlog/_plan/260903_gemini_38_rollout/001_vendor_claim_ledger.md
  • devlog/_plan/260903_gemini_38_rollout/002_live_cca_probe.md
  • devlog/_plan/260903_gemini_38_rollout/003_audit_round1_synthesis.md
  • devlog/_plan/260903_gemini_38_rollout/004_no_change_inventory.md
  • devlog/_plan/260903_gemini_38_rollout/005_audit_round2_synthesis.md
  • devlog/_plan/260903_gemini_38_rollout/006_maintainer_review_fold.md
  • devlog/_plan/260903_gemini_38_rollout/007_closeout.md
  • devlog/_plan/260903_gemini_38_rollout/010_wp1_antigravity_core.md
  • devlog/_plan/260903_gemini_38_rollout/020_wp2_metadata_pricing.md
  • devlog/_plan/260903_gemini_38_rollout/030_wp3_peripheral_surfaces.md
  • devlog/_plan/260903_gemini_38_rollout/040_wp4_delivery.md
  • devlog/_plan/260903_gemini_38_rollout/050_followups.md
  • devlog/_plan/260903_muse_release_train/000_plan.md
  • devlog/_plan/260903_muse_release_train/005_audit_round1_synthesis.md
  • devlog/_plan/260903_muse_release_train/010_wp1_regression_review.md
  • devlog/_plan/260903_muse_release_train/011_review_ledger.md
  • devlog/_plan/260903_muse_release_train/020_wp2_muse_mark.md
  • devlog/_plan/260903_muse_release_train/030_wp3_preview_release.md
  • devlog/_plan/260903_muse_release_train/040_wp4_main_release.md
  • devlog/_plan/260903_muse_release_train/050_followups.md
  • devlog/_plan/260903_muse_spark_plan_oauth/000_plan.md
  • devlog/_plan/260903_muse_spark_plan_oauth/001_vendor_claim_ledger.md
  • devlog/_plan/260903_muse_spark_plan_oauth/002_plan_credential_feasibility.md
  • devlog/_plan/260903_muse_spark_plan_oauth/003_credential_and_quota_measurements.md
  • devlog/_plan/260903_muse_spark_plan_oauth/004_muse_quota_emission_questions.md
  • devlog/_plan/260903_muse_spark_plan_oauth/010_wp1_direct_provider.md
  • devlog/_plan/260903_muse_spark_plan_oauth/020_wp2_device_oauth.md
  • devlog/_plan/260903_muse_spark_plan_oauth/030_wp3_delivery.md
  • devlog/_plan/260903_muse_spark_plan_oauth/040_wp4_muse_oauth_provider.md
  • devlog/_plan/260903_muse_spark_plan_oauth/050_wp5_passive_muse_quota.md
  • docs-site/src/content/docs/guides/cursor-private-inference.md
  • docs-site/src/content/docs/guides/providers.md
  • docs-site/src/content/docs/guides/sidecars.md
  • docs-site/src/content/docs/reference/cli/lifecycle.md
  • docs-site/src/content/docs/reference/configuration.md
  • docs-site/src/content/docs/reference/management-api.md
  • docs-site/src/content/docs/troubleshooting/disk-usage-temp-files.md
  • gui/public/provider-icons/README.md
  • gui/src/hooks/useJsonConfigEditor.ts
  • gui/src/i18n/de.ts
  • gui/src/i18n/en.ts
  • gui/src/i18n/fr.ts
  • gui/src/i18n/ja.ts
  • gui/src/i18n/ko.ts
  • gui/src/i18n/ru.ts
  • gui/src/i18n/tr.ts
  • gui/src/i18n/zh-TW.ts
  • gui/src/i18n/zh.ts
  • gui/src/oauth-tos-risk.ts
  • gui/src/pages/Logs.tsx
  • gui/src/pages/Providers.tsx
  • gui/src/pages/integrations/CursorIntegrationPage.tsx
  • gui/src/pages/integrations/cursor-api.ts
  • gui/src/pages/providers-shared.ts
  • gui/src/pages/use-dashboard-data.ts
  • gui/src/provider-icons.ts
  • gui/src/styles-integrations.css
  • gui/src/styles.css
  • gui/tests/cursor-integration-page.test.tsx
  • gui/tests/dashboard-contracts.test.ts
  • gui/tests/fr-localization.test.ts
  • gui/tests/locale-parity.test.ts
  • gui/tests/logs-auto-refresh.test.tsx
  • gui/tests/oauth-tos-warning-gate.test.tsx
  • gui/tests/provider-icons.test.ts
  • gui/tests/use-json-config-editor.test.tsx
  • gui/tests/viewport-scroll-caps.test.ts
  • package.json
  • scripts/model-metadata.source.json
  • scripts/privacy-scan.ts
  • scripts/test.ts
  • skills/ocx/references/05_remote_hub.md
  • src/adapters/cursor/catalog.ts
  • src/adapters/cursor/claude-id.ts
  • src/adapters/cursor/discovery.ts
  • src/adapters/cursor/effort-map.ts
  • src/adapters/google.ts
  • src/adapters/openai-responses.ts
  • src/cli/claude.ts
  • src/cli/connect.ts
  • src/cli/registry.ts
  • src/cli/status.ts
  • src/client/connect.ts
  • src/client/hub-client.ts
  • src/clients/config-export.ts
  • src/codex/catalog.ts
  • src/codex/catalog/aggregation.ts
  • src/codex/catalog/metadata.ts
  • src/codex/catalog/parsing.ts
  • src/codex/catalog/provider-fetch.ts
  • src/codex/convergence-types.ts
  • src/codex/desired-state.ts
  • src/combos/failover.ts
  • src/combos/index.ts
  • src/combos/resolve.ts
  • src/config.ts
  • src/grok/sync.ts
  • src/integrations/cursor-effort-table.ts
  • src/integrations/state.ts
  • src/integrations/writer.ts
  • src/lib/app-owned-memory-stores.ts
  • src/lib/bounded-body.ts
  • src/oauth/generic-account-failover.ts
  • src/oauth/index.ts
  • src/oauth/meta-muse.ts
  • src/providers/antigravity-models.ts
  • src/providers/command-code-efforts.ts
  • src/providers/free-directory.ts
  • src/providers/registry.ts
  • src/responses/code-mode-helper-compat.ts
  • src/responses/state.ts
  • src/server/auth-cors.ts
  • src/server/chat-completions.ts
  • src/server/chat-native.ts
  • src/server/claude-messages.ts
  • src/server/effort-row.ts
  • src/server/index.ts
  • src/server/management/api-key-rotation.ts
  • src/server/management/api-key-usage.ts
  • src/server/management/context.ts
  • src/server/management/cursor-integration-routes.ts
  • src/server/management/logs-usage-routes.ts
  • src/server/management/provider-routes.ts
  • src/server/management/route-registry.ts
  • src/server/management/usage-aggregate-cache.ts
  • src/server/management/usage-summary-cache.ts
  • src/server/models-capabilities.ts
  • src/server/responses/core.ts
  • src/types/config.ts
  • src/types/tools.ts
  • src/usage/expected-prices.ts
  • src/usage/ledger-scanner.ts
  • src/usage/log.ts
  • src/usage/summary.ts
  • src/web-search/index.ts
  • structure/05_gui-and-management-api.md
  • tests/api-key-attribution.test.ts
  • tests/api-keys-routes.test.ts
  • tests/api-usage.test.ts
  • tests/bridge-legacy-shell-normalization.test.ts
  • tests/chat-completions-endpoint.test.ts
  • tests/ci-workflows.test.ts
  • tests/claude-cli.test.ts
  • tests/cli-status-json.test.ts
  • tests/client-connect.test.ts
  • tests/codex-catalog.test.ts
  • tests/codex-convergence-contract.test.ts
  • tests/codex-desired-state.test.ts
  • tests/combos.test.ts
  • tests/command-code-provider.test.ts
  • tests/cursor-catalog.test.ts
  • tests/cursor-claude-id.test.ts
  • tests/cursor-discovery.test.ts
  • tests/cursor-display-names.test.ts
  • tests/cursor-effort-rows.test.ts
  • tests/cursor-effort-suffix.test.ts
  • tests/cursor-effort-table.test.ts
  • tests/cursor-integration-status.test.ts
  • tests/cursor-local-models-schema.test.ts
  • tests/cursor-umbrella-rows.test.ts
  • tests/fixtures/openai-provider-option-migration-child.ts
  • tests/gemini-37-flash-migration.test.ts
  • tests/gemini-web-search.test.ts
  • tests/google-adapter.test.ts
  • tests/google-antigravity-wire.test.ts
  • tests/google-hardening.test.ts
  • tests/google-models-listing.test.ts
  • tests/grok-models-effort-list.test.ts
  • tests/grok-sync.test.ts
  • tests/helpers/ci-watchdog.ts
  • tests/helpers/enforce-pr-target-harness.ts
  • tests/integrations-writer.test.ts
  • tests/kiro-pool-rank.test.ts
  • tests/lab-fabric-task.test.ts
  • tests/legacy-shell-compat.test.ts
  • tests/memory-watchdog.test.ts
  • tests/meta-model-api-provider.test.ts
  • tests/meta-muse-oauth.test.ts
  • tests/muse-spark-web-search-compat.test.ts
  • tests/oauth-provider-reconcile.test.ts
  • tests/oauth-tos-warning.test.ts
  • tests/openai-provider-option-e2e.test.ts
  • tests/opencode-go-muse-context.test.ts
  • tests/opencode-go-muse-vision.test.ts
  • tests/privacy-scan-meta-key.test.ts
  • tests/provider-config-batch-management.test.ts
  • tests/provider-model-discovery-contract.test.ts
  • tests/provider-registry-parity.test.ts
  • tests/provider-workspace-auth.test.ts
  • tests/remote-catalog.test.ts
  • tests/responses-custom-tool-repair.test.ts
  • tests/responses-state.test.ts
  • tests/responses-undeclared-tool-guard.test.ts
  • tests/server-combo-failover-e2e.test.ts
  • tests/settings-stream-mode.test.ts
  • tests/usage-aggregate-cache.test.ts
  • tests/usage-cost.test.ts
  • tests/usage-ledger-scanner.test.ts
  • tests/usage-summary.test.ts

📝 Walkthrough

Walkthrough

This PR bundles several independent efforts: a PR carry-attribution hygiene gate with a Co-authored-by check, a Cursor Private Inference effort-table/effort-row feature, an additive Gemini 3.8 Flash rollout, new Meta Model API/Meta Muse Code providers, a streaming usage-ledger rewrite, combo failover cooldown refinements, an atomic provider-config batch editor, and numerous smaller runtime fixes (transient retry budgets, catalog timeouts, dashboard URLs, write_stdin bridging, max_output_tokens propagation, log-table layout).

Changes

PR Carry-Attribution Hygiene Gate

Layer / File(s) Summary
Carry-detection module
.github/scripts/pr-carry-attribution.cjs, .github/scripts/pr-carry-attribution.test.cjs
Detects carry/supersede/reimplement language referencing another PR and verifies matching Co-authored-by trailers.
Referenced-author resolver
.github/scripts/pr-referenced-authors.cjs, .github/scripts/pr-referenced-authors.test.cjs
Resolves referenced PR authors and commit identities via the GitHub API, bounded and fail-open.
Hygiene gate wiring
.github/scripts/pr-hygiene.cjs, .github/workflows/enforce-pr-target.yml, .github/workflows/pr-hygiene.yml, tests/ci-workflows.test.ts, tests/helpers/enforce-pr-target-harness.ts
Adds missing_coauthor_credit failure, attribution-approved label, edited trigger, and commit-fetching in workflows.
Documentation & CREDITS.md
AGENTS.md, CONTRIBUTING.md, README.md, CREDITS.md, devlog/_plan/260903_contributor_credit_restoration/*
Adds the attribution rule and the contributor-credit ledger.

Estimated code review effort: 4 (Complex) | ~75 minutes

Cursor Effort Table and Effort Rows

Layer / File(s) Summary
Bundle effort-table parser
src/integrations/cursor-effort-table.ts, tests/cursor-effort-table.test.ts
Parses Cursor's minified main.js bundle for its effort ladder, with caching and static fallback.
Claude id normalizer
src/adapters/cursor/claude-id.ts, catalog.ts, effort-map.ts, discovery.ts
Canonicalizes Anthropic/version-first Claude id spellings into one capability base.
Effort-row grammar & server wiring
src/server/effort-row.ts, chat-completions.ts, claude-messages.ts, responses/core.ts, management/cursor-integration-routes.ts
Adds <base-id>--<effort> selectors for table-less models across Responses, Chat, and Messages.
Effort prediction & config flag
src/server/models-capabilities.ts, src/config.ts, src/types/config.ts
Adds predictCursorEffort and the cursorEffortRows opt-in flag.
GUI provenance, i18n, docs
gui/src/pages/integrations/CursorIntegrationPage.tsx, gui/src/i18n/*.ts, docs-site guide
Shows bundle/static provenance and table-less hints in the dashboard.

Estimated code review effort: 4 (Complex) | ~90 minutes

Gemini 3.8 Flash Rollout

Layer / File(s) Summary
Antigravity wire model & adapter guard
src/providers/antigravity-models.ts, registry.ts, src/adapters/google.ts
Lands 3.8 additively alongside 3.7 with suffix-tier wire ids and widened paragraph-rejection guard.
Metadata & pricing
scripts/model-metadata.source.json, src/usage/expected-prices.ts, src/web-search/index.ts
Adds the 3.8 metadata record and pricing overlays.
Tests
tests/gemini-37-flash-migration.test.ts, tests/google-*.test.ts
Covers ladder, retirement, and paragraph-guard regressions.

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

Meta Model API and Meta Muse Code Providers

Layer / File(s) Summary
Registry & pricing
src/providers/registry.ts, command-code-efforts.ts, expected-prices.ts
Adds meta-model (key auth) and meta-muse (OAuth import) registry entries.
OAuth credential import
src/oauth/meta-muse.ts, src/oauth/index.ts
Imports the Muse Code CLI Keychain credential with defaultRefreshPolicy: "disabled".
Privacy-scan detector
scripts/privacy-scan.ts
Adds a meta-api-key finding for `LLM
GUI branding & tests
gui/src/provider-icons.ts, oauth-tos-risk.ts, various tests
Adds the Meta mark and high-risk ToS classification.

Estimated code review effort: 4 (Complex) | ~60 minutes

Usage Ledger Streaming Rewrite

Layer / File(s) Summary
Cooperative ledger scanner
src/usage/ledger-scanner.ts
Streams the ledger in 1 MiB chunks with checkpoint digests instead of full reads.
Usage aggregate cache
src/server/management/usage-aggregate-cache.ts
Retains bounded numeric aggregates and appends incrementally.
Streaming summary accumulator
src/usage/summary.ts
Rewrites summarizeUsage/projectUsageSummary on a streaming accumulator.
Routes & docs
api-key-usage.ts, logs-usage-routes.ts, usage-summary-cache.ts, structure/05_gui-and-management-api.md
Wires /api/usage and /api/keys onto the new cache.

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

Combo Failover and Cooldown Refinement

Layer / File(s) Summary
Cooldown & retry-after parsing
src/combos/failover.ts, index.ts, src/oauth/generic-account-failover.ts
Adds HTTP-date parsing and short request-rate cooldowns.
Target selection & scope
src/combos/resolve.ts, src/server/responses/core.ts
Adds provider-scoped cooldown and cached-quota exclusion.

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

Atomic Provider Config Batch Editor

Layer / File(s) Summary
Provider editor DTO
src/server/auth-cors.ts
Adds an exhaustive field-policy map and fail-closed parser.
PUT /api/providers route
src/server/management/provider-routes.ts, route-registry.ts
Adds an atomic batch-update route with baseline conflict detection.
GUI save flow
gui/src/hooks/useJsonConfigEditor.ts
Sends { baseline, next } in one request instead of a full-config PUT.

Estimated code review effort: 4 (Complex) | ~60 minutes

Independent Runtime Fixes and Test Infrastructure

Layer / File(s) Summary
Release workflow permissions
.github/workflows/release.yml, service-lifecycle.yml
Grants missing job permissions and fixes trigger paths.
Chat-native transient budget
src/server/chat-native.ts
Bounds upstream sends across 429 retry and key rotation.
Claude proxy liveness retry
src/cli/claude.ts
Retries the liveness probe before spawning a duplicate proxy.
Continuation write-storm fix
src/responses/state.ts
Limits background persistence to one write attempt per cadence.
write_stdin tool bridging
src/responses/code-mode-helper-compat.ts, src/types/tools.ts
Wraps write_stdin through the declared exec tool.
Catalog inactivity timeout
src/cli/connect.ts, src/client/hub-client.ts, src/lib/bounded-body.ts
Adds --catalog-timeout for stalled remote-hub downloads.
Status dashboard URL
src/cli/status.ts
Derives the URL from hub origin or hostname instead of localhost.
Hub loopback client sync
src/codex/desired-state.ts, src/grok/sync.ts
Allows a hub with an enabled loopback listener to sync integrations.
max_output_tokens propagation
src/codex/catalog/*.ts, src/server/models-capabilities.ts
Threads an output-token ceiling through catalog discovery.
Logs table layout
gui/src/pages/Logs.tsx, styles.css
Fixes a ten-column layout to stop virtualizer jitter.

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

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 74 / 80

이 PR은 기여자 기능 PR이 아니다. 지금 dev 끝점 81a1fc1cc (패키지 버전 2.41.0) 를 preview 에 올리고, 채널 버전만 2.41.0-preview.20260903 으로 바꾸는 메인테이너 미리보기 승격이다. 가지 이름은 codex/promote-preview-2410 이고 base는 preview 다. tip d60434946 과 현재 origin/dev 의 제품 트리 차이는 package.json 버전 한 줄뿐이다. 2.41.02.41.0-preview.20260903. 로컬에서 git diff --stat origin/dev d60434946 결과가 그 한 줄과 같다.

싣는 화물은 v2.40.0 이후 dev 에 이미 들어온 37커밋이다. 계획과 커밋별 위험 등급은 devlog/_plan/260903_muse_release_train/011_review_ledger.md 에 있다. 본문이 말한 R3 네 줄(Muse 자격증명 import #3337, reusable-workflow 권한 #3262, pull_request_target 기여 게이트 #3318, provider editor 원자 저장 #3296)은 이미 dev 에 머지된 상태다. 같은 기차의 큰 헤드라인은 Meta Model API #3321, Muse Code CLI OAuth(meta-muse) #3337, Meta 브랜드 마크 #3338, Gemini 3.8 Flash 롤아웃, Cursor Private Inference effort table/행/max_output_tokens, 버그 라벨 drawdown, CREDITS.md·기여 트레일러 게이트다. 이 PR이 새로 짠 제품 코드가 아니다. 미리보기 채널에 그 tip을 올리는 승격이다.

봇이 제목에 [WRONG BRANCH] 를 붙이고 enforce-target 을 빨갛게 만든 것은 버그가 아니다. .github/workflows/enforce-pr-target.ymlALLOWED_BASES["dev"] 뿐이라 base=preview 인 승격은 설계상 실패한다. 선례는 #3260 · #3001 · #3072 · #3073 이다. 계획서 030_wp3_preview_release.md 1번도 같은 말을 적는다. 베이스를 dev 로 바꾸면 승격 의미가 사라진다. 화면 사진 요구도 이 승격에는 해당 없다. PR이 초안(draft)으로 남은 것도 그 게이트가 초안을 유지한 알려진 패턴이다.

지금 GitHub merge 상태는 CONFLICTING / DIRTY 다. 원인은 package.json 하나뿐이다. preview tip 49812c9e8 은 채널 버전 2.40.0-preview.20260902 이고, 이 가지 tip은 2.41.0-preview.20260903 이다. .github/workflows/release.yml 은 자동 병합된다. #3264 가 미리보기에 cherry-pick 한 #3262 권한 블록과, dev#3269 가 넣은 service-lifecycle 경로(release.yml 자체)가 한 파일에서 합쳐진다. 버전 줄 충돌만 풀면 된다. 풀 때는 tip 값 2.41.0-preview.20260903 을 남긴다. scripts/release.ts 503줄 규칙은 preview 가지에서 -preview. 접미가 없으면 거절한다.

프리플라이트는 리뷰 시점에도 그대로다. npm @bitkyc08/opencodex@2.41.0-preview.20260903 은 404, v2.41.0* 태그는 원격에 없고, dist-tags 는 latest=2.40.0 / preview=2.40.0-preview.20260902 이라 이번 버전이 채널을 앞으로만 민다. 로컬 전체 스위트는 메인테이너 지시로 돌리지 않았고, 그래서 scripts/release.ts 헬퍼 대신 030_wp3_preview_release.md 수동 경로를 탄다. 머지 뒤에는 tip의 40자 SHA 로 preview push-event CI 와 service-lifecycle 이 둘 다 초록인지 확인한 다음 release.ymlexpected-sha 로 디스패치해야 한다. PR-event 런만으로는 release.yml 게이트가 받지 않는다.

types.ts/config.ts 분할을 이유로 닫을 대상이 아니다. 중복 승격도 아니다. 미리보기 채널이 목적이다. 라벨은 바꾸지 않는다. ToS 고위험 OAuth 동의는 브라우저에만 있는 잔여(050_followups.md)이고, anthropic · google-antigravity 와 같은 노출이라 이번 퍼블리시 차단 사유가 아니다.

package.json 라인 3 - preview2.40.0-preview.20260902 와 tip 의 2.41.0-preview.20260903 이 충돌한다. tip 쪽 문자열을 남기고 충돌을 풀어라. -preview. 접미가 빠지면 scripts/release.ts 가 거절한다.
경로 base preview / head codex/promote-preview-2410 - 81a1fc1cc 를 조상으로 두고 버전 한 줄만 올린 승격이다. 새 제품 코드를 얹지 않는다.
경로 GitHub Actions / enforce-target - 승격 PR을 기여자 PR로 본 자동 잠금이다. 베이스를 dev 로 바꾸지 마라. 화면 사진을 요구하지 마라.
경로 merge 상태 CONFLICTING / DIRTY - package.json 만 충돌한다. release.yml 은 자동 병합된다. preview 를 승격 가지에 한 번 합친 뒤 tip 버전을 남기면 된다.
경로 CI / draft - 게이트가 초안을 유지하고 enforce-target 은 실패한다. admin merge 가 맞고, 머지 후 push-event CI·lifecycle 을 tip SHA 로 다시 본다.
경로 devlog/_plan/260903_muse_release_train/050_followups.md - ToS 서버 동의·Muse 구독 사용량 표시는 후속 유닛이다. 이번 채널 차단 사유가 아니다.
경로 현재 dev 81a1fc1cc - tip 과 버전 줄만 다르다. 재베이스로 제품을 더 얹을 필요는 없다. 충돌만 풀면 된다.

메인테이너의 판단이 필요한 지점

  • package.json 충돌을 승격 가지에 preview 를 합쳐서 풀지, GitHub UI에서 tip 버전만 남기고 풀지
  • 봇이 붙인 [WRONG BRANCH] / enforce-target 빨강을 무시하고 base=preview 로 admin merge 할지. 선례 #3260 과 같다
  • 머지 직후 tip 40자 SHA 로 push-event CI·service-lifecycle 초록을 기다린 뒤 release.yml 디스패치할지(계획서 4–6번)
  • ToS 서버 동의 유닛을 이 기차에 끼울지. 계획서는 후속으로 둔다
  • 이 PR 을 dev 로 다시 겨냥할지. 하지 말 것. 그러면 승격이 아니다

너의 추천
package.json 충돌만 tip 버전 2.41.0-preview.20260903 으로 풀고, [WRONG BRANCH]·enforce-target 빨강은 무시하고 base=preview 로 admin merge 하라. 머지 뒤 git ls-remote origin preview 로 tip SHA를 다시 읽고, 그 SHA의 push-event CI와 service-lifecycle 이 초록이면 gh workflow run release.yml --ref preview -f version=2.41.0-preview.20260903 -f tag=preview -f expected-sha=<sha> 로 디스패치하라. 베이스를 dev 로 바꾸지 마라. types/config 분할이나 중복을 이유로 닫지 마라. 라벨은 바꾸지 않는다.

이 댓글은 grok-bot이 작성했습니다

@lidge-jun

Copy link
Copy Markdown
Owner Author

Superseded by the merge-based promotion branch: preview carries 20 commits of prior promotion history that dev does not, so a branch pinned at the dev SHA cannot merge cleanly. Reopened as a dev-into-preview merge, which is the shape #3260 and #3264 used.

@lidge-jun lidge-jun closed this Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants