Skip to content

Release v3.8.50 - #8875

Merged
diegosouzapw merged 3047 commits into
mainfrom
release/v3.8.50
Aug 26, 2026
Merged

Release v3.8.50#8875
diegosouzapw merged 3047 commits into
mainfrom
release/v3.8.50

Conversation

@diegosouzapw

@diegosouzapw diegosouzapw commented Jul 28, 2026

Copy link
Copy Markdown
Owner

🚀 Release v3.8.50

This pull request merged the reconciled release/v3.8.50 snapshot into main on
2026-08-26. It is a record of what entered main; it is not evidence that the v3.8.50 tag,
GitHub release, deployment, or homologation has been completed.

The authoritative per-change ledger and release attribution live in
CHANGELOG.md[3.8.50].

📊 Reconciled release ledger

Category Count
✨ New Features 183
🐛 Bug Fixes 795
📝 Maintenance 194
Total documented changes 1,172

These counts are the top-level bullets physically present in the v3.8.50 CHANGELOG section at
PR head 091589089cd134a94df9f6cdab9ba562b2cefd18. The Contributors table is deliberately
excluded from the documented-change total.

At the same head:

  • v3.8.49..HEAD: 3,057 commits total / 2,964 non-merge;
  • canonical cycle ed2db6cb19^..HEAD: 1,715 commits total / 1,626 non-merge /
    89 merges;
  • release-base GitHub PR census: 1,405 merged, 227 closed-unmerged, and 2 open.

The physical tag range and canonical development-cycle range are separate denominators because
the repository topology makes history outside the canonical cycle reachable from the tag range.

Highlights

  • First-class unified search across API and agent surfaces, with source-aware errors, bounded
    upstream handling, and SSRF hardening.
  • Expanded A2A, OmniConductor, MCP, Codex App Server, cloud-agent, CLI, Electron, and
    editor-integration surfaces.
  • Broader multimodal routing across image, speech, music, OCR, and video, including the accepted
    Video Bridge baseline tracked by closed issue
    #9760.
  • Quota-aware and strict-zero-cost scheduling, resilient fallback, connection/model isolation,
    and 19 public routing strategies with 14-factor Auto-Combo scoring.
  • Security, privacy, streaming-fidelity, SQLite, provider, packaging, deployment, and
    cross-platform hardening, with original contributor credit retained in the full ledger.
  • Public documentation, provider/reference data, diagrams, contributor rankings, and dated
    community metrics refreshed from physical sources.

Measured product surface

The current source-backed denominators are intentionally kept separate:

  • 352 registered providers, including 154 with hasFree: true discovery metadata;
  • chat model registry: 268 providers, 2,566 provider-model pairs, and 1,312 raw model
    IDs
    ;
  • free-budget catalog: 455 rows, 40 recurring pools, and 56 recurring/keyless
    free-forever providers
    ;
  • 19 routing strategies and 14 Auto-Combo factors;
  • 110 MCP tools across 33 scopes;
  • 159 database migrations.

🙌 Contributors and credit

The v3.8.50 CHANGELOG ledger contains 247 contributor rows: the maintainer plus 246 external
contributors
.

The separate all-history README census contains 639 normalized human Git identities:

  • 407 commit authors, including the maintainer;
  • 232 coauthor-only identities from explicit Co-authored-by trailers.

That all-history census is a dated 2026-08-24 measurement. It normalizes GitHub noreply handles,
excludes bot/agent/service/placeholder identities, and does not collapse ordinary e-mail identities
merely because their display names match. It is intentionally distinct from the release ledger and
the GitHub-attributed contributor rankings.

Video Bridge baseline and follow-up hardening

Closed issue #9760 remains the accepted
Video Bridge baseline. The later FU01–FU09 workstreams are hardening follow-ups; they are not
retroactive acceptance criteria for that closed baseline.

Follow-up Pull request Status at this reconciliation
FU01 #11362 MERGED
FU02 #11344 MERGED
FU03 #11382 MERGED into FU01 and carried into the release branch
FU04 #11383 MERGED
FU05 #11680 OPEN / DRAFT
FU06 #11681 OPEN / DRAFT
FU07 #11381 MERGED
FU08 #11369 MERGED
FU09 #11350 MERGED

FU05 and FU06 were still open drafts against release/v3.8.50 after #8875 merged. Their content
must not be described as part of merge commit b4ec7807abe03de41173251f345b6bf9a3993bc5.

License and provenance status

There are two different surfaces and they must not be conflated: the clean exact-head dependency
policy passed, while provider source/provenance still requires an owner disposition.

Production dependency policy — PASS on clean exact-head CI

The clean-install Lint job at exact PR head inspected the production dependency graph and passed:

  • 951 production packages inspected;
  • 947 allowed directly;
  • 4 registered exceptions;
  • 0 policy violations.

An earlier local scan reported 1,120 packages and six apparent violations:

@giscus/react, @pierre/diffs, @pierre/theming, @splinetool/runtime, chroma-js, and
elkjs. That result is classified DRIFT / INFRA, not a repository policy failure: those
packages existed only under the polluted shared node_modules/.pnpm tree and were absent from
package.json, package-lock.json, and node_modules/.package-lock.json. The clean CI dependency
graph is authoritative for the exact-head gate.

Registered exceptions already present in policy are
@img/sharp-libvips-linux-x64@1.3.2 (LGPL-3.0-or-later),
@img/sharp-libvips-linuxmusl-x64@1.3.2 (LGPL-3.0-or-later),
caniuse-lite@1.0.30001784 (CC-BY-4.0), and
tls-client-node@0.2.0 (Custom/LICENSE, Commons Clause). They remain registered exceptions for
periodic review; tls-client-node retains its documented medium-risk classification.

Provider source/provenance — HOLD

This is separate from the npm dependency scan. The following source-derived provider families
still require an owner decision about license-obligation compliance, replacement, removal, or
deferral:

  • Raycast Relay;
  • Hailuo Web;
  • Felo Web;
  • Qwen Web v2;
  • Microsoft Designer Web;
  • EdgeTTS;
  • Gemini Web image-only.

The merged release head does not contain the unpublished removal patch for these seven
families. This body therefore makes no provider-removal completion claim.

Exact-head validation snapshot

The classifications below are bound to PR head
091589089cd134a94df9f6cdab9ba562b2cefd18; a partial, cancelled, skipped, stale, or advisory
result is not promoted to PASS.

Status Gate / evidence Result
PASS CI test matrix Unit 8/8, E2E 9/9, Integration 2/2, Vitest, Ecosystem E2E, Security Tests, and Bun SQLite on Ubuntu/Windows completed successfully.
PASS Build and packaging evidence CI Build, Electron package smoke on Ubuntu/Windows, Package Artifact, and the opencode-plugin Node 22/24 + Build jobs completed successfully.
PASS Quality and documentation Lint, Docs Sync, docs advisory lint, i18n validation/glossary/UI coverage, Quality Gates Extended, Quality Ratchet, PR Test Policy, Change Classification, DAST smoke, and CI Dashboard completed successfully.
PASS External Semgrep Cloud semgrep-cloud-platform/scan completed successfully.
BASE-RED / ADVISORY Protocol Clients E2E The advisory live-server lane failed; exact-base comparison also failed, so this is not attributed as a PR-only regression. It still is not PASS.
CANCELLED / HOLD Coverage job Coverage merge/report/gate steps succeeded, but the informational Codecov upload was cancelled and the aggregate job conclusion is cancelled. No consolidated PASS is claimed.
INFRA-RED / HOLD Fast Production Build The workflow concluded startup_failure; its job was cancelled before runner allocation and has no executed steps. This is not evidence of a product build failure, and it is not a PASS.
INFRA-RED / HOLD GitHub-hosted Semgrep workflow The workflow concluded failure while its only job remained anomalously queued without a runner or executed steps. The separate external Semgrep Cloud scan passed, but that does not promote this lane to PASS.
PASS Production dependency licenses Clean exact-head CI inspected 951 packages: 947 directly allowed, 4 registered exceptions, and 0 violations.
DRIFT / INFRA Local dependency scan A polluted shared node_modules/.pnpm tree inflated the graph to 1,120 packages and produced six non-lockfile findings. It is not used as the exact-head verdict.
SKIPPED SonarQube Skipped; no PASS is claimed.
NOT RUN Local VPS homologation No documented exact-build homologation evidence was produced before merge.

Accordingly, this snapshot is not represented as release-green.

Post-merge status

  • Release v3.8.50 #8875 was squash-merged by diegosouzapw at 2026-08-26T17:25:02Z.
  • PR head: 091589089cd134a94df9f6cdab9ba562b2cefd18.
  • Merge commit and current main at the post-merge readback:
    b4ec7807abe03de41173251f345b6bf9a3993bc5.
  • release/v3.8.50 remained at 091589089cd134a94df9f6cdab9ba562b2cefd18.
  • No v3.8.50 Git tag or GitHub Release existed at readback.
  • No release deployment or Local VPS homologation was completed; the consequent deploy path was
    skipped.
  • Publish to Docker Hub
    run 32994073275
    was cancelled.
  • Radar Export
    run 32994073198 was
    cancelled.
  • Wiki Sync completed before cancellation handling and pushed wiki commit 326a9c9 with 3
    insertions / 3 deletions
    . Its measured cover counts were providers 352, strategies 19,
    locales 43, and mcpTools: null; null is unknown/not extracted, not zero.
  • FU05 #11680 and FU06
    #11681 remained OPEN / DRAFT against
    release/v3.8.50.
  • The clean exact-head production dependency policy remained PASS; the separate seven-family
    provider source/provenance decision remained HOLD.
  • At the body-publication snapshot, the post-merge main CI, Build App, and Release-Green
    continuous workflows were still in progress. Semgrep and opencode-plugin CI had passed;
    OpenSSF Scorecard had failed. These mixed/in-progress results do not establish release-green
    status.

Publication remains incomplete until the unresolved provider source/provenance decision, non-PASS
gate classifications, exact-build homologation, and final owner-controlled tag/release steps
receive an explicit, evidence-backed disposition.

Evidence identifiers

release PR: #8875
PR state: MERGED
merged at: 2026-08-26T17:25:02Z
merged by: diegosouzapw
PR head / release branch: 091589089cd134a94df9f6cdab9ba562b2cefd18
PR-head tree: a07d3db5d12e77bd1c8e8b88ed87efaf6756c1d1
squash merge commit / main readback: b4ec7807abe03de41173251f345b6bf9a3993bc5
main merge tree: a07d3db5d12e77bd1c8e8b88ed87efaf6756c1d1
previous release tag: v3.8.49
previous tag peeled commit: c9d4a45f1883d7daf150bbff631f3e83b41aa5b4
canonical cycle open: ed2db6cb19ba534980c5b3e046501e8a5c40c458
pre-publication evidence readback: 2026-08-26T17:42:26Z

@steveepreston

Copy link
Copy Markdown

Hey, please if possible do a release, we are died behind v3.8.48 still

Comment thread open-sse/services/adobeFireflyBrowserLogin.ts Fixed
Comment thread src/app/api/services/dario/admin/import-from-omniroute/route.ts Fixed
Comment thread docker/devin-bridge/network-guard/proxy.mjs Dismissed
Comment thread open-sse/services/raycast.ts Dismissed
Comment thread open-sse/services/raycast.ts Fixed
Comment thread open-sse/services/tokenRefresh/providers/copilot.ts Dismissed
Comment thread scripts/raycast/extract-credentials.mjs Dismissed
Comment thread open-sse/executors/devin-cli-agentic.ts Dismissed
Comment thread open-sse/executors/raycast.ts Fixed
Comment thread open-sse/executors/raycast.ts Dismissed
Comment thread open-sse/services/raycast.ts Dismissed
Comment thread tests/unit/antigravity-quota-host-8965.test.ts Dismissed
Comment thread tests/unit/antigravity-quota-host-8965.test.ts Dismissed
Comment thread tests/unit/antigravity-quota-host-8965.test.ts Dismissed
Comment thread tests/unit/antigravity-quota-host-8965.test.ts Dismissed
Comment thread open-sse/executors/raycast.ts Dismissed
@diegosouzapw

Copy link
Copy Markdown
Owner Author

Thanks for sending this PR. Please resolve mandatory items and retest. Re-open after cleanup if scope/mergeability blocking is fixed.

@diegosouzapw diegosouzapw changed the title Release v3.8.50 [defer] Release v3.8.50 Aug 9, 2026
@diegosouzapw diegosouzapw added merge-train-deferred PR ejetada do merge-train — triagem separada deferred-v3.8.50 Adiada para o ciclo v3.8.50 (validacao VPS, refactor, ou escopo grande) labels Aug 9, 2026
@diegosouzapw

Copy link
Copy Markdown
Owner Author

Thanks for sending this PR. Please resolve mandatory items and retest. Re-open after cleanup if scope/mergeability blocking is fixed.

@diegosouzapw

Copy link
Copy Markdown
Owner Author

test step5

@diegosouzapw

Copy link
Copy Markdown
Owner Author

Thanks for sending this PR. Please resolve mandatory items and retest. Re-open after cleanup if scope/mergeability blocking is fixed.

1 similar comment
@diegosouzapw

Copy link
Copy Markdown
Owner Author

Thanks for sending this PR. Please resolve mandatory items and retest. Re-open after cleanup if scope/mergeability blocking is fixed.

Comment thread open-sse/services/adobeFireflyBrowserLogin.ts Dismissed
Comment thread open-sse/services/adobeFireflyBrowserLogin.ts Fixed
Comment thread open-sse/services/adobeFireflyClient.ts Fixed
Comment thread open-sse/services/adobeFireflyClient.ts Fixed
Comment thread open-sse/services/adobeFireflyClient.ts Fixed
Comment thread open-sse/services/adobeFireflyClient.ts Fixed
Comment thread open-sse/services/adobeFireflySession.ts Dismissed
@diegosouzapw diegosouzapw changed the title [defer] Release v3.8.50 Release v3.8.50 Aug 9, 2026
Comment thread src/shared/middleware/chatBodyAdmission.ts Fixed
Comment thread src/shared/middleware/chatBodyAdmission.ts Fixed
`next dev` writes and re-adds this block (see
node_modules/next/dist/server/lib/generate-agent-files.js), so leaving it out of
a diff only recreates the uncommitted change on the next dev run. Committing it
keeps the working tree clean, which is what the block's own note prescribes.
Release reconciliation (Phase 0a.1). `scripts/release/aggregate-changelog.mjs`
folds each changelog.d/<section>/*.md fragment into its heading in the living
[3.8.50] section and deletes the fragment, which is the whole point of the
fragment convention: two PRs never touch the same file, so the CHANGELOG never
conflicts mid-cycle and no bullet is eaten by a merge auto-resolve.

Section bullets 731 -> 1041. The remaining uncovered commits (mostly merges from
#11397 onward, which landed without a fragment) are reconciled separately.
…ered commits

Adds 131 consolidated bullets (45 features, 71 fixes, 15 maintenance)
covering the ~490 user-facing commits and the ~100 chore/ci/test/refactor/docs
commits that landed in the cycle without a CHANGELOG entry, grouped by
subsystem and citing their PR references.

Uncovered report: 594 -> 175 (the remainder are commits carrying no #N in
their subject, which the matcher can never resolve; they are covered in
prose).
…ABRT OOM

The 8 unit shards run under V8 coverage instrumentation, which retains far more
memory than the bare suite. With the 4096 MB ceiling they began aborting with
exit 134 ("Ineffective mark-compacts near heap limit") at ~4086 MB as the
provider catalogue grew during the v3.8.50 cycle: every test in the shard passed
and the process died at the end, which reads as a test failure without being one.

Aligns test:unit:ci:shard and test:unit:serial with the 8192 MB the non-sharded
variants already use. GitHub-hosted runners have 16 GB, so the headroom is real.
Validated by the CI run on this commit — the shards are the gate.

Refs #10692
…nt suppressions

The previous heap bump only touched test:unit:ci:shard, i.e. the node the shard
script spawns. The process that actually runs out of memory is the `c8` wrapper
around it — it aggregates ~577 MB of raw V8 coverage JSON — so the ceiling stayed
at the V8 default (~4 GB) and the shards kept aborting at ~4083 MB, byte for byte
the same failure. Setting NODE_OPTIONS on the step covers c8 and every child,
which is the pattern the coverage-merge job already uses.

Also prunes three eslint suppression entries whose violations no longer exist:
videoBridgeContactSheet.ts and videoBridgeRuntime.ts (no-unused-vars, fixed
during this cycle) and cli-oneproxy-commands.test.ts (no-explicit-any 14 -> 13,
a consequence of restoring the real mock in that test). Stale entries make
`npm run lint` exit 2 with 'There are suppressions left that do not occur
anymore'. Pruned and verified on an uncontaminated checkout, not the devbox.

Refs #10692
…tegration tests

All four predate this session — each reproduces identically on f95b03d (2026-08-24),
so none is a cycle regression. Draining them here because the release pre-flight is
where inherited reds get resolved.

1. Package Artifact: the job runs `build:cli`, which assembles dist/ but never writes
   dist/BUILD_SHA — only `build:release` does, via write-build-sha.mjs. The #10427
   provenance guard inside check:pack-artifact then rejects the artifact as
   untraceable, and rejects it even under OMNIROUTE_ALLOW_CANARY_BUILD. The job's
   build+validate pair was structurally incompatible and failed 100% of the time.
   Stamps the SHA between the two steps.

2. Electron Package Smoke: electron/package.json's build.files allowlist enumerates
   each lib/*.js by hand and never got lib/loginHeaderCapture.js, added alongside its
   require() in #9984. The file therefore stayed out of app.asar and the packaged app
   died at startup on 'Cannot find module ./lib/loginHeaderCapture'.

3. proxy-pipeline: the breaker assertion grepped chat.ts for executeChatWithBreaker(,
   but that call moved behind the chatDispatch.ts seam. Rather than drop the check,
   it now pins both hops — chat.ts dispatches through the seam and the seam calls the
   breaker — so the extraction cannot silently take the breaker off the path.

4. skills-pipeline: #9058 began encoding skill tool names as omr_skill_<base64url>
   because providers require ^[a-zA-Z0-9_-]+$, and these assertions still expected the
   raw name@version. They now derive the expected name from encodeSkillToolName(), the
   same helper production uses, so the test tracks the contract instead of duplicating
   it. Only the assertions about names on the wire were converted; the identifiers
   passed straight to skillExecutor.execute() stay raw, because those are not encoded.

Integration suite for these two files: 54/55. The one still red —
'web_search fallback preserves Responses API output' — is a separate pre-existing
defect, deliberately left failing rather than papered over: on the /v1/responses path
resolveSearchCredentials() returns null for the seeded serper-search connection, so
executeWebSearch.ts:185-200 falls through to the cheapest fallbackOnly provider
(duckduckgo-free) and the results come back empty. The sibling chat-path test seeds
identically and does resolve serper-search. Needs its own investigation.

Refs #10692
…en mcp shard

None of these are cycle regressions. The Vitest job runs test:vitest (mcp shard)
then test:vitest:ui; the mcp shard was failing on a missing glm-5.3-max and aborted
the job before the ui shard ever ran. Fixing that shard this cycle unmasked 34 ui
failures that had been broken since 18-23 Aug — four separate PRs that moved a
contract and updated their own tests but not their siblings.

- ProviderCard gained useRouter() in #10448; four test files render it without
  mocking next/navigation and died on 'invariant expected app router to be mounted'.
  The sibling created alongside #10448 already had the mock — it just was not
  applied to the other four consumers.

- SkillCoverage gained a required config category. The four fixtures in
  agent-skills-page still described only api/cli, so the component read
  config.have off undefined. Values were chosen per scenario rather than pasted:
  full coverage gets 2/2 so its bar stays emerald, the amber fixture gets 3/4 so it
  stays amber. CoverageBar renders api -> config -> cli, so the new bar lands in the
  MIDDLE and the cli assertions moved from index [1] to [2]; without that the cli
  checks would have passed while measuring the config bar. The aria test now pins
  all three bars.

- CliAgentsPage hardcoded AGENT_IDS, which had already drifted once (6 -> 8 with
  omp/letta, per its own comment) and drifted again with prime-agent (#11166). It is
  now derived from CLI_TOOLS. This is why an agent missing from that list is not
  cosmetic: it never enters the status map, defaults to not_installed, and adds a
  phantom card to the filter and count tests. Deriving keeps the fixture in sync by
  construction instead of waiting for the next agent.

- claudeTlsClient asserted proxyUrl was undefined inside a test literally named
  'falls back to env var when per-call proxyUrl not provided' — it pinned the old
  behaviour where testOverride bypassed proxy resolution. #10910 moved resolution
  ahead of the override on purpose ('so test overrides and the real path both see
  it'), so the assertion now checks the fallback the test name promises.

test:vitest:ui goes from 34 failures to 14. The remaining 14 sit in six files none
of this commit touches (AutoComboCatalog, CoolingConnectionsPanel, ProxyRegistryManager
x2, connectionsSearchFilter) plus one claudeTlsClient case that passes in isolation
and only fails in the full run — i.e. cross-file pollution. They need a clean
environment to judge: this devbox resolves part of its tree through a stray pnpm
store and has already produced one phantom failure count this cycle.

Refs #10692
…enerate the omni-inference skill

Two gates in the Lint job, both inherited — each was hidden behind the one before it.

i18n value drift: #11283 rewrote sidebar.trafficInspectorSubtitle in en.json without
touching the 42 translations, so 32 locales kept serving a sentence the English no
longer says. Most take the documented __MISSING__: placeholder, which makes the
runtime serve the corrected English until the translation pipeline catches up.
Three do not:

  - vi cannot take a placeholder at all — tests/unit/i18n-vi-completeness.test.ts
    bans any __MISSING__/__TODO__ value outright, so it needs a real translation.
  - pt and pt-BR are translated for real rather than placeheld, because a placeholder
    there means this project's own maintainer reads the sidebar in English.

Each file changed by exactly one line; the JSON was not reserialised wholesale.

agent-skills-sync: skills/omni-inference/SKILL.md was missing the ElevenLabs voices
and speech-to-text routes added by #11312, so the generator reported one file out of
date and the gate exited 2. Regenerated — purely additive, 48 lines, no deletions.

Verified: check-ui-value-drift PASS, i18n:check-ui-coverage PASS (42 locales),
i18n-vi-completeness 5/5, check:agent-skills-sync 46 unchanged.

Refs #10692
… the per-test budget

The remaining ui-shard reds were one class, not six bugs: every one of them did
`await import(<heavy component>)` INSIDE an `it()`, so Vite's transform of the
dependency graph was billed to that test's timeout. Measured costs against the
budgets they had to fit in:

  ProxyRegistryManager   86s import vs 30s / 60s / 5s budgets (render itself: 567ms)
  claudeTlsClient       ~12s import vs 5s default
  useProviderConnections  1050-line hook, whole dashboard graph, vs 5s default

That is why they looked like cross-file pollution: on an idle box the import
squeaked under the limit, and under the ui suite's 20 parallel workers it did not.
Running claudeTlsClient ALONE on a loaded box reproduces it — the trigger is CPU
contention, not a neighbouring file. The sibling chatgptTlsClient/grokTlsClient
tests import the same graph and never fail, because they import statically at
module scope, where the cost falls on the collection phase which has no per-test
budget. Every fix here does the same: static import or a beforeAll with its own
budget.

AutoComboCatalog also explains its own blast radius: the timeout aborted inside an
open act(), leaking an unbalanced act scope that then failed the file's three
remaining tests in ~20ms with 'overlapping act() calls'. One slow import, four reds.

CoolingConnectionsPanel is the one production change. It imported providerText from
the ../providerPageHelpers barrel, but that symbol is DEFINED in the
../providerCredentialText leaf and only re-exported by the barrel — which drags
providerRegistry (352 providers) and the rest of the provider-page graph into a
"use client" component for one string helper. Verified before accepting: the
component used nothing else from the barrel, the barrel has no top-level
side-effect to lose (the empty-registry hazard this repo has hit before does not
apply), typecheck:core is clean, and the panel's first test drops from ~4s to 95ms.
The import was suboptimal, never broken — the screen was not failing for users.

No assertion was weakened anywhere. expect() counts are unchanged (25/25, 4/4) or
up by one (AutoComboCatalog 11 -> 12); the #8855 autofill sentinels, the
data-1p-ignore / data-lpignore guards and the dead-status round-trip are intact.

The #5918 TDZ guard was proven still live by mutation, not by absence of red:
moving useProxyBatchOperations(load) above its const reproduced
'ReferenceError: Cannot access load before initialization' in 207ms, then the
production file was restored (diff empty).

tests/unit/ui under load: 17 failed files / 45 failed tests -> 4 failed files /
4 failed tests, none of them these. The four left are compression-guidance-7530,
compressionPanel, compressionUltraTier and lobe-provider-icons-stepfun, untouched
and uninvestigated.

Refs #10692
…acts that moved

All three are the sibling-test gap again: a PR moved a contract, updated its own
tests, and left these behind. None is a production defect — in two of the three the
production side is a deliberate security fix.

v1-contracts-behavior (4 failures, one cause): the job env sets INITIAL_PASSWORD,
which makes isAuthRequired() true, and #9320 (b07182c) made the /v1 catalogue
gate on-by-default instead of opt-in via settings.requireAuthForModels. The four
contract reads were calling the catalogue routes with no credential and correctly
getting 401. Bisected the job's four env vars to confirm INITIAL_PASSWORD alone
reproduces it (5 pass / 4 fail with it, 9 / 0 without). The tests now send a Bearer
token; the shape assertions are untouched, and the auth contract itself stays owned
by tests/unit/v1-models-auth-leak-9320.test.ts rather than being duplicated here.

opencode-config-startup: two independent drifts. OPENCODE_VERSION was pinned to
1.18.8 while the installed opencode-ai is 1.18.18 (Dependabot 7f69589, #10626) —
now read from require("opencode-ai/package.json").version, which is exactly as
strict but cannot drift on the next bump. And the no-limit-metadata case asserted
limit === undefined, but #11054 made the generator always emit a limit; it now pins
the actual fallback {context: 128_000, output: 8_192} instead of an absence.

memory-pipeline: #11040 (GHSA-cpv3-xr7r-xf8q) made the resolved caller principal
always win over a caller-supplied apiKeyId, so a spoofed id can no longer write into
another principal's store. That PR updated the unit sibling but not this one. The
test now asserts the stronger property — and deliberately not just the absence: the
spoofed principal's store is empty AND the caller can still read the entry, which
proves the write was redirected rather than dropped and keeps the emptiness check
from passing vacuously with a disabled store. (The old assertion was count === 0,
which a switched-off memory store would satisfy.)

Assertion counts: 43 -> 43, 13 -> 14, 76 -> 81. Nothing weakened or removed.
Verified: 24/24 pass, with and without the CI env vars.

Refs #10692
…e2e assertion and 10 integration reds

Electron Package Smoke — a packaging defect that had been hidden behind another
packaging defect for nine days. Once the loginHeaderCapture fix let the main process
start, the server underneath died on 'Cannot find module next': resources/app/server.js
shipped without resources/app/node_modules.

electron-builder discards the ROOT node_modules in code, not by configuration —
app-builder-lib/out/util/filter.js:42 has a hard-coded `if (relative === "node_modules")
return false` that runs before any filter pattern. The second extraResources entry
pointing INTO ../.build/electron-standalone/node_modules is what sidesteps it, because
those relative paths are never equal to "node_modules". #10325 removed that entry as an
apparent duplicate and flipped the test to assert "exactly once", freezing the
regression as if it were the contract. Restored, and the unit guard now pins both
entries — proven by mutation: reverting package.json to the post-#10325 shape fails the
guard 3/4, restoring it passes 4/4.

group-b-quota-plans-config — the assertion was impossible to satisfy on ANY route, and
the page was never broken. layout.tsx hands the whole message catalogue to
NextIntlClientProvider, React serialises that prop into the RSC payload, and en.json
carries "Internal Server Error" twice, so page.content() always contains it: probing
/dashboard, /dashboard/costs, /dashboard/settings and /login showed the string present
with every page rendering fine, and a pageerror probe on the failing run captured zero
client exceptions. This is the same trap that killed the sibling not.toContain("500")
in fc77100 ("raw HTML is unreliable") — that one was removed, this one was kept.
Now asserts on rendered text, which still catches a real error boundary. The pageerror
capture stays: the CI failure carried no stack trace, which is why it was misread twice.

Integration — 10 of the 14 shard-2 reds, all sibling-test gaps behind security fixes:
monitoring health now takes a Request and requires management auth (GHSA-mvf8-qc78-5mxm);
the OAuth import routes moved to requireManagementAuth (GHSA-mg76) — the test accepts
both guard shapes and gained a stronger anchor that every exported handler awaits a
guard on its own request, mutation-verified; skill tool names are derived from
encodeSkillToolName() and the fake upstream now returns the encoded name so
decodeSkillToolName() is exercised too; previous_response_id now fails closed (#10262);
proxy_logs persist as an async batch (#11182) so the test flushes first;
providerQuotaOverrides joined GET /api/resilience (#9871); the reasoning fixture used a
model that stopped being thinking-incompatible, replaced and pinned with a premise
assert so it cannot rot silently again.

A vacuous assert.ok(true, "all 10 streams completed without hanging") was replaced with
real anchors — content must arrive on every stream and the active Timeout count must not
grow.

Four are deliberately left red rather than aligned, each now tracked: #11551 (the
/v1/models after() wiring is dead — the route passes a third argument to a two-parameter
function and catalogCache never imports after, so the #8728 contract is unimplemented),
#11552 (~27% of requests emit an extra discarded upstream call; the delivered
distribution is exactly 0.70, so weighted routing is correct and the waste is the real
finding), the fixed-account combo pin (aligning it would destroy the per-step attribution
the test exists for), and the web_search fallback already tracked as #11524.

Package Artifact — the provenance stamp I added last round used git rev-parse HEAD, which
under pull_request is the ephemeral merge commit and therefore never an ancestor of the
release branch. Now takes the PR head sha.

Refs #10692
…ive the test

Vitest went red on 'synchronizes upstream models only when autoFetchModels is
explicitly true' with new URL throwing inside a fetch dispatched from
Timeout._onTimeout (useProviderModels.ts:69). It is intermittent: green in the two
previous CI runs, green every time in isolation, red only under the ui suite's 20
parallel workers.

The hook schedules its auto-sync in a setTimeout whose callback only checks the
 flag on entry — and that flag stays false while the component is
mounted. Both tests asserted first and unmounted last, so under contention the
timer escaped the test window, fired after afterEach had already run
vi.unstubAllGlobals(), and reached the REAL fetch with a relative URL.

Unmounting before the assertions closes the window: cleanup flips , the
callback returns early, and the calls already recorded on fetchMock are still there
to assert against. No assertion changed.

Not a regression from this cycle — the file's last change is fd76271 (#10603).
Fixed rather than tracked because an intermittent red in a blocking job is worse
than a permanent one: it teaches people to re-run instead of to look.

Refs #10692
…free

When no explicit provider is requested and the auto-selected cheapest
provider has no credentials, executeWebSearch ran the fallbackOnly loop
first. duckduckgo-free (costPerQuery 0, authType none) always won there
with an empty credentials object, so a configured paid connection such as
serper-search was silently ignored and the caller got success:true with
zero results.

Move the sweep for other credentialed regular providers ahead of the
fallbackOnly loop (and exclude fallbackOnly ids from it, so a free
last-resort provider never outranks a configured one on cost). The
fallbackOnly loop stays as the true last resort.

The chat path only appeared correct because duckduckgo-free happened to
fail there and handleSearch retried the alternate provider; on
/v1/responses it "succeeded" with no results.

Closes #11524
…refresh

`/v1/models` has passed a third argument to `getUnifiedModelsResponse()`
(`{ scheduleBackgroundRefresh: (task) => after(task) }`) ever since #10198, but
#9199 had already removed the parameter: the function takes two, so the object
was silently dropped and `catalogCache` kept scheduling the stale-while-
revalidate rebuild with `setTimeout(..., 0)`. The builder is overwhelmingly
synchronous under the single-threaded App Router, so it pinned the event loop
before the stale response was flushed — the #8728 guarantee did not exist.

- `catalogCache` now imports `after` from `next/server` and exposes
  `defaultBackgroundRefreshScheduler`, which defers to `after()` and falls back
  to a macrotask outside a Next request scope (instrumentation warm-up, tests).
- `resolveCachedCatalogResponse` takes `scheduleBackgroundRefresh` and
  `getStaleWhileRevalidateMs` on its existing options object.
- `getUnifiedModelsResponse` accepts the options object the route already passes
  and propagates the scheduler.

The excess argument was invisible to CI: `tsconfig.typecheck-core.json` is a
curated 27-file allowlist, `check:dashboard-typecheck` only covers
`src/app/(dashboard)`, and `next.config.mjs` sets `ignoreBuildErrors: true`.

Closes #11551
A universal handoff whose summary comes back unparseable persists nothing,
so shouldGenerateUniversalHandoff keeps answering "generate" and the very
next model switch in the same session re-issues the same full-history
summarization call and discards the answer again — forever.

With a switch-heavy combo strategy (weighted, random, round-robin, p2c) the
models alternate on almost every turn, so that background call lands on a
large fraction of requests: an upstream call whose response nobody reads is
real money on a paid provider and real quota on a metered one. Measured on
the weighted 70/30 matrix, that inflated the observed openai share to 0.895
where routing actually delivered 0.70, and at the unit level 199 of 200
model switches issued a fresh discarded summarization call.

Back off per (session, combo) after an answer that is not a usable handoff:
exponential 5min -> 1h, cleared on the first successful generation, capped
at 500 tracked keys. Deliberately narrow — a transient upstream failure
(!response.ok) is NOT tracked, so it still retries on the next switch, which
is the behavior the context-relay path already depends on.

After the fix, same harness at n=200: 201 upstream calls for 200 requests
(1 extra, 0.5%), and the measured openai share equals the delivered share
(0.725). The unit guard drops 199 discarded calls to 1.

Refs #11552
… on fallback

A combo step pinned with an explicit `connectionId` (or a request pinned via
`x-omniroute-connection`) is an operator instruction, not a hint. The generic
account-fallback branch in handleSingleModelChat excluded the pinned connection
after an upstream failure and re-selected a sibling account of the same provider,
so a priority combo repeating one provider/model with two different fixed accounts
ran both attempts under the FIRST step: the second step, with its own pin, never
executed and per-step attribution (comboStepId / comboExecutionKey) was wrong.

Gate the rotation on `!hasForcedConnection`, matching the antigravity
stream-readiness, pre-response-timeout and account-semaphore branches that already
let pinned steps fall through to combo orchestration. Cooldown recording via
markAccountUnavailable is unchanged, and unpinned selection still excludes burned
connections.

Refs tests/integration/combo-routing-e2e.test.ts
…test

The guard added for #10427 checks ancestry against origin/main by default. That
is the right ref at publish time (npm-publish.yml runs on main), but in a
pull_request context it can never hold: while the PR is open its head is by
construction not an ancestor of main, and the shallow checkout does not even
bring origin/main into the local graph, so the probe answers false regardless.
The job therefore failed 100% of the time and only became visible now that it
stopped being cancelled behind Build.

Pre-merge the one checkable invariant is that the stamp matches the branch under
test, so resolve the ref from refs/pull/<N>/head — which exists on origin even
for fork PRs, unlike head.ref, which only exists on the author's repository.
PR #9870 moved the proxy-registry bulk-assign action into the toolbar's
"More actions" (…) overflow menu, which renders its items only while open.
The e2e smoke flow still clicked the testid directly, so the locator never
resolved and the test burned its full 180s budget.

Open the menu first and assert it is visible before clicking; every existing
assertion is unchanged.
…uilder

PR #8285 (global model search) replaced the expert-mode "Manual model"
block positionally with the new GlobalModelSearchPanel, dropping the only
way to type a provider/model pair by hand in expert mode. The supporting
state and handlers (manualModelInput, manualModelError,
manualModelHasDuplicate, handleAddManualModel) survived as dead code, so
neither typecheck nor lint flagged the loss.

Re-render the block above the search panel, unchanged from its pre-#8285
form. Regression guard: tests/e2e/combos-flow.spec.ts "expert mode shows
a single-page combo form with manual model entry", which had been failing
with a 180s timeout on locator.fill for combo-manual-model-input.
b07182c (#9320) inverted the catalog auth rule: /v1/models now requires auth
whenever management auth is configured, unless requireAuthForModels is explicitly
false. The e2e harness boots with INITIAL_PASSWORD set, so the endpoint has been
answering 401 since 2026-08-04 and this check has been red ever since — invisible
only because the job kept being cancelled behind Build.

Mirror the sibling /api/providers check in this same file: assert the catalog
shape whenever the catalog is actually served, and otherwise pin the auth gate by
status AND error type, so a 401 from an unrelated misroute cannot pass for the
deliberate one.
… manual-model restore

Two changes, both consequences of restoring the expert-mode manual model block:

- no-unused-vars drops 9 -> 6. Three of those suppressions existed only because
  #8285 deleted the JSX that consumed manualModelInput / handleAddManualModel and
  left the state behind as dead code. With the block back they are live again, and
  a stale suppression makes eslint exit 2 — which is what actually turned the Lint
  job red, not the react-hooks errors below.
- Freeze the 7 react-hooks/set-state-in-effect + 1 react-hooks/immutability errors
  in this file. They are pre-existing: overwriting the file with its 091e2ba
  content reproduces all 8. They surfaced only because touching the file evicted it
  from the persisted .eslintcache the Lint job restores; 223 errors across 127
  tracked files are masked the same way repo-wide. Fixing seven effects inside a
  4841-line page during a release is the wrong trade, so they are frozen here and
  tracked separately.
Covers 37d784b..7afacf6, which landed on release/v3.8.50 after the
CHANGELOG had already been reconciled and were therefore missing from the
release notes: the search-connection preference (#11524), the /v1/models
after() injection point (#11551), the discarded universal-handoff
re-summarization (#11552), per-step connection pins on fallback, the
expert-mode manual model entry in the combo builder, plus the CI/e2e/eslint
follow-ups.

No CHANGELOG.md edit — aggregation stays with the release captain.
The fragment claimed 199 of 200 model switches fired a discarded call. That
number is the TDD proof — summarization calls counted with the new guard
disabled — not the observed waste. The measured run was ~254 upstream calls for
200 requests, i.e. roughly one request in four, dropping to 201 after the fix.
Also state explicitly that the delivered 70/30 share was correct all along, so
the entry cannot be read as a routing defect.
…ck-boot)

check:pack-boot booted the packed tarball fine but failed its version
assertion: `version "undefined" (expected "3.8.50")`. The artifact was
not broken — the health payload was.

#11040 (GHSA-mvf8-qc78-5mxm) reduced GET /api/monitoring/health to a
liveness-only view for non-management callers. But that route is
classified PUBLIC (src/shared/constants/publicApiRoutes.ts:67) and
runAuthzPipeline deletes CLI_TOKEN_HEADER from the forwarded headers for
EVERY route class (src/server/authz/pipeline.ts), so a handler can only
learn that a local CLI authenticated from the subject the policy stamped.
publicPolicy stamped `anonymous` unconditionally, so requireManagementAuth
in the route 401'd even for a valid loopback machine token and every
caller got `{status, setupComplete}` — no `version`.

Verified on the installed tarball before the fix: the same token that
returns 200 on /api/cli/whoami (MANAGEMENT, where the pipeline does stamp
the subject) got the anonymous body on /api/monitoring/health.

publicPolicy now stamps the same loopback-gated `local-cli-token` subject
the MANAGEMENT policy already produced. The shared verdict (peer-locality
resolution + constant-time token compare) moves to
src/server/authz/peerContext.ts so both policies use one implementation —
no behavior change on the MANAGEMENT side. Anonymous callers keep the
liveness-only view.

Tests: publicPolicy stamps the CLI subject only for a loopback peer with a
valid token (both negative controls asserted; the positive case fails on
the pre-fix policy), plus a health-route pin that a stamped local-CLI
caller receives `version`. check:pack-boot green end to end (boot #1,
machine-token contrast, sql.js round trip, boot #2 persistence).
…t-spoofing guard

Two follow-ups to the peerContext extraction, both caught by CI.

public-policy.test.ts guarded its two loopback cases behind
`if (!getMachineTokenSync()) t.skip(...)`. Those are the only tests covering the
branch the pack-boot fix added, so a machine-id that failed to resolve would have
silenced exactly the coverage that matters and shipped the authorization change
untested — which is what the test-masking detector flagged. The sibling
management-policy cases in authz/routeGuard.test.ts call the same helper with no
guard and pass in CI, so an empty token is a broken environment: assert on it
loudly instead of skipping.

route-guard-private-lan.test.ts pinned its Host-spoofing regression guard to
source text inside management.ts; moving requestPeerAddress into peerContext.ts
made the literals disappear from the file it greps, while the property itself was
untouched. The guard now follows the implementation: neither module may read the
Host header, the owning module must resolve the token-stamped peer, and — new
positive anchor — management.ts must still delegate to peerContext, so the guard
cannot pass by the policy quietly regrowing a Host-based path.
…e was hiding

The Lint job restores a persisted .eslintcache whose key hashes
config/quality/eslint-suppressions.json. Touching that file to prune a stale
entry changed the key, the tree was relinted cold, and 215 errors across 126
tracked files surfaced at once. None of them are new: they reproduce on
091e2ba and were simply being served from cache for every file a PR did not
happen to touch. See #11600.

Freezing them trades an accidental hiding place for an explicit, versioned
record: new violations now fail the gate, old ones are listed by file and rule
and can be drained deliberately. Fixing 215 react-hooks findings across 126
dashboard components mid-release — none of which have a test pinning their
current behaviour — would be a far worse trade than recording them.

Scope was filtered on purpose rather than using --suppress-all, which wanted to
freeze 846 violations across 246 paths including _artifacts/, .playwright-cli/
and output/ — none of which exist in a CI checkout — and among them 28
no-new-func, the Hard Rule #3 rule. Only git-tracked files are frozen here, and
no-eval / no-new-func / no-implied-eval / no-restricted-imports are excluded by
construction: had any appeared in a tracked file the script would have reported
it instead of suppressing it. None did.

Verified over the tracked file set only (the CI checkout's scope): 0 errors.
… a local run

ee24799 froze 215 'pre-existing' errors measured on this devbox. The CI
artifact from run 32968510623 shows what actually happens on a clean npm ci:
ZERO unsuppressed errors and 150 suppressions that match nothing. The react-hooks
findings those entries described do not fire in CI at all — they are an artifact
of this machine's node_modules, the same phantom-count problem that once produced
a fabricated '925 errors' locally.

So the Lint job was not red because the repo had 215 hidden errors. It was red
because I froze errors that only exist on my machine, and ESLint exits 2 on a
suppression that no longer matches anything.

Rebuilt from the CI artifact (messages + suppressedMessages, which is where a
suppressed violation still shows up) instead of from a local lint: every count is
now the value CI observes. 150 rules removed, 102 files emptied and dropped, back
to the 1249 entries that predate the freeze. RequestLoggerV2's exhaustive-deps
keeps the committed 6 rather than the 7 I derived — 6 is the value the artifact
run demonstrably accepted, and a count above the real one is exactly what turns
this gate red.

The 9->6 prune in 7afacf6 stays: those three were genuinely dead, freed by
restoring the manual-model block, and CI agrees.
@diegosouzapw
diegosouzapw merged commit b4ec780 into main Aug 26, 2026
48 of 52 checks passed
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.