✨ New Features
- feat(providers): add Yuanbao (web) as a cookie-session provider (#6196) —
yuanbao-web(Tencent Yuanbao,yuanbao.tencent.com) with cookie-only auth (hy_user/hy_token+ public agent id), SSE→OpenAI translation incl.reasoning_content, exposing DeepSeek V3/R1 + Hunyuan / Hunyuan-T1. Regression guard:tests/unit/providers-yuanbao-web.test.ts.together-webwas deferred (no verifiable web-session endpoint — needs a captured request) andhuggingchat-webdropped (the existinghuggingchatalready is a web-cookie provider). (thanks @chirag127) - feat(providers): route the built-in agentrouter through the dynamic Claude-Code wire image (#6056) — a small static allow-set (
CC_WIRE_IMAGE_BUILTINSinopen-sse/services/ccWireImageBuiltins.ts), consulted byisClaudeCodeCompatible/isClaudeCodeCompatibleProvider/applyFingerprint, makes agentrouter adopt the CC wire-image headers + fingerprint while guarding the CC baseUrl/auth branches so it keeps its own registrybaseUrlandx-api-keyauth. Regression guard:tests/unit/agentrouter-cc-wire-image.test.ts(asserts the wire image is applied AND agentrouter's baseUrl/auth are preserved). Live WAF-acceptance against agentrouter.org is a VPS validation follow-up (Hard Rule #18). - feat(providers): bulk-add API keys for Cloudflare Workers AI (#6174) —
cloudflare-aiis removed from the bulk-add exclusion list and the bulk parser gains a 3-fieldname|accountId|apiKeymode; the bulk route now builds a per-entryproviderSpecificDataso each key carries its ownaccountId(fixing the previous shared-object reuse), and both the create + key-validation paths receive it. Regression guard:tests/unit/bulk-api-key-parser-cloudflare.test.ts. (thanks @muflifadla38) - feat(dashboard): routing/settings UX clarity (#6147) — (1) weighted combos show the effective routing share % next to each weight when weights don't sum to 100 (
WeightTotalBar.tsx); (2) the status widget's user-facing "Cloud Sync" label is renamed to "Remote Settings Sync" (CloudSyncStatus.tsx; internal ids/state untouched); (3) built-in providers gain an opt-in advanced base-URL override (isBaseUrlOverrideEligibleProvider, hidden behind an "Advanced" toggle, reusing the existingproviderSpecificData.baseUrlpersistence — not globally widened). Regression guard:tests/unit/routing-settings-ux-6147.test.ts. - feat(combo): add an option to disable session stickiness, per-combo or globally — round-robin / random combos can rotate to a different connection on every request instead of pinning a whole conversation to one connection by its first-message hash. Resolution precedence per-combo
config.disableSessionStickiness→ globalsettings.disableSessionStickiness→ defaultfalse(preserves the #3825 prompt-cache/504 fix); gates both stickiness call sites inopen-sse/services/combo.ts. Exposed as a global toggle (Combo Defaults) and a per-combo Inherit/on/off control. (#6168) Regression guard:tests/unit/combo-disable-session-stickiness.test.ts. (thanks @RCrushMe) - feat(docker): add the
OMNIROUTE_NO_SUDOenv flag for root-less / user-namespaced deployments — the MITM cert-trust command path (resolveSudoSpawninsrc/mitm/systemCommands.ts) now strips the leadingsudowhen the flag is truthy, in addition to the existing root / sudo-missing cases, so the Proxy Agent runs withoutsudo(the operator trusts the CA manually, e.g. viaNODE_EXTRA_CA_CERTS). Argv-arrayspawnpreserved — no shell interpolation (Hard Rule #13). (#6122) Regression guard:tests/unit/mitm-systemCommands-no-sudo.test.ts. (thanks @powellnorma) - feat(providers): add Requesty as an OpenAI-compatible gateway provider (BYOK, base
https://router.requesty.ai/v1, ~200 free requests/day) — wired through the shared OpenAI-compatible registry with full model passthrough (open-sse/config/providers/registry/requesty/,src/shared/constants/providers/apikey/gateways.ts). (#6120) Regression guard:tests/unit/requesty-provider.test.ts. (thanks @chirag127) - feat(dashboard): add configured-only / available-only filters to the Free Provider Rankings page (#6150) — hide providers you haven't configured, or whose connections are all rate-limited / out of quota, via server-side query params (
?configuredOnly/?availableOnlyonGET /api/free-provider-rankings) backed by a testable lib helper reusing the in-process connection state (no Redis). Both filters default off, so the default view is unchanged; this supersedes the earlier client-side "Configured Only" toggle (#6245) with an available-only dimension and unit-tested logic. Regression guard:tests/unit/freeProviderRankings-filters.test.ts. - feat(rankings): add a 'Configured Only' filter to the Free Provider Rankings page, so the table can be narrowed to just the providers you have configured connections for (with an empty-state hint when none are configured). New
en.jsonkeys and a pure filter helper covered bytests/unit/free-provider-rankings-configured-filter.test.ts. (#6245, closes #6150 — thanks @Iammilansoni)
🔧 Bug Fixes
-
fix(mitm): the test suite and CI can never mutate the OS trust store again —
OMNIROUTE_SKIP_SYSTEM_TRUST=1(set by the global test setup and all CI workflows) makesinstallCert/uninstallCert/installTproxyCaskip the privileged OS dispatch while preserving the #4546 environment-skip contract. Root cause of the self-hosted runner incident: a cert-flow integration test installed a 105-byte fake PEM into/usr/local/share/ca-certificates, breaking ALL system TLS on the VM. Regression guard:tests/unit/system-trust-test-guard.test.ts. (#6310) -
fix(security):
/api/keys/{id}/devicesanswers a clean method-first 405 for undocumented HTTP methods (e.g. the newQUERY) via a dedicatedhttp-method-guardrule — the auth layer was answering 401 first, failing schemathesis's unsupported-methods check. Same pattern as the v3.8.44 TRACE fix. Regression guard:tests/unit/dast-method-not-allowed.test.ts. -
fix(combo): the #6216 empty-stream failover is restricted to truly empty bodies (zero bytes — the Gemini HTTP-200-empty case), restoring the #3399/#3685 pass-through contracts for
[DONE]-terminated empty streams and incomplete Claude lifecycles. New guard:#5976 truly EMPTY streaming body → invalid for combo failover(87/87 across both suites). -
fix(combo): 5 streaming-path fixes — locked-stream 500, error-frame-only-if-no-content, Gemini
MALFORMED_RESPONSE→content_filter failover, correlationId substring search, per-model-500 lockout skip + request-logger UI detail. Maintainer follow-up:releaseQualityClonecancels the abandoned quality-check tee branch (per-request memory) + regression test. (#6216 — thanks @hartmark) -
fix(skills): generate the missing
omni-github-skillsregistry entry (the #6186 catalog addition never ran the generator — 8 integration assertions split between old/new counts) and align the agent-skills catalog counts across integration + unit suites (43 = 23 API + 20 CLI; 44 with config). -
fix(a2a): finish the #6186 catalog-count update —
listCapabilitiesmetadata reportedcoverage.api.total: 22(type literal + value) andSkillCoverageSchemapinnedz.literal(22), so the schema would REJECT the correct runtime value with 23 API skills. All three aligned to 23. -
fix(github-skills): add a missing import, unit tests and a settings JSON-parse fix for the GitHub agent-skill discovery/import flow. (#6186 — thanks @Moseyuh333)
-
fix(api):
POST /api/github-skillsvalidates its body with a Zod schema (validateBody) instead of blindrequest.json()destructuring — a non-arraytargetswould crash.map. Regression guard:tests/unit/github-skills-route-validation.test.ts. -
fix(docker): add
id=to the BuildKit cache mounts so strict builders (e.g. buildkitd with strict frontend parsing) accept the Dockerfile. (#6291 — thanks @karimalsalah) -
fix(oauth): register
zedin the OAuthPROVIDERSmap (fixes "Unknown provider" on the Zed sign-in flow) (#6078 — thanks @anki1kr), and alignzedinOAUTH_PROVIDER_IDS+ the config enum after the merge. -
fix(doubao-web): switch the Doubao web provider to the Dola global endpoint. (#6235 — thanks @backryun)
-
fix(doctor): resolve two false-positive WARNs in the doctor diagnostics (#6163, closes #6162 — thanks @arssnndr)
-
fix(providers): refresh the GitHub Copilot model catalog to the current upstream set. (#6154 — thanks @backryun)
-
fix(providers): correct the Kiro model catalog to real upstream ids — fabricated
claude-opus-4.7/claude-sonnet-4.6entries removed, realclaude-sonnet-5/claude-sonnet-4.5/claude-haiku-4.5kept. (#6170) -
feat(sse): surface Kiro adaptive-thinking reasoning frames as
reasoning_contentin the OpenAI-shaped stream. (#6213 — thanks @VXNCXNX) -
fix(cli): use
OMNIROUTE_SERVER_HOSTinstead of the POSIX auto-setHOSTNAMEfor the bind address (fixes wrong bind on POSIX shells that export HOSTNAME). (#6195, closes #6194 — thanks @Theadd) -
feat(provider): add Claude 5 Sonnet to the Claude Web provider catalog. (#6209, closes #6200 — thanks @Iammilansoni)
-
fix(providers): add
nvidiatoPROVIDER_TOOL_LIMITS(1536) to prevent silent tool-list truncation. (#6177 — thanks @LuisAlejandroVega) -
fix(translator): strip the
reasoningparam for nvidiaz-ai/glm-5.2(upstream 400s on it). (#6181 — thanks @kanztu) -
fix(dashboard): providers page gains a data-timeout guard and the live-WS standalone wiring (no more indefinite spinner when the data fetch stalls). (#6211)
-
fix(sse): surface the ChatGPT-web image silent-drop as an accurate error instead of an empty success. (#6208)
-
fix(cline): force upstream streaming for Cline/ClinePass (streaming-only API) — non-stream client requests are served from the buffered SSE. (#6165)
-
fix(dashboard): remove the always-on Auto-Routing (combo) banner from the home page — it did not reflect live routing state and reappeared on every fresh browser. Replacement guard:
tests/unit/home-no-autorouting-banner.test.ts. (#6164) -
fix(dashboard): stop a model-test error from freezing the page (React #31 object-as-child toast) — errors go through
extractApiErrorMessage. (#6161) -
fix(oauth): extract the keychain-import-only guard to its own module, restoring the oauth file-size freeze. (#6158)
-
fix(sse): strip zero-width markers from streamed tool-call arguments — a follow-up to #5857. That PR removed injected zero-width joiners (U+200D) from streamed assistant text/reasoning but deliberately left tool-call argument JSON byte-exact. The request-side obfuscation (
open-sse/services/claudeCodeObfuscation.ts) injects ZWJ into agent words — including the temp path inside the Bash tool description — and Claude models copy that verbatim into generated commands, which are delivered as tool-call arguments rather than assistant text. As a result the ZWJ survived and corrupted code blocks (e.g. a temp path rendered with an invisible joiner). Nowopen-sse/handlers/responseSanitizer.tsstrips zero-width code points from tool-call argument strings at every emit site (OpenAI non-stream/stream chattool_calls+ legacyfunction_call, native Responsesfunction_callitems, the OpenAI→Responses conversion, and the native Responses streamingresponse.function_call_arguments.delta/.doneevents). Only zero-width code points are removed; JSON structure and all other bytes stay identical (no parse/restringify), so normal arguments remain byte-exact. Regression guard: 6 new cases intests/unit/response-sanitizer.test.ts(suite 50/50). -
fix(nodejs): the default app log path now resolves under
DATA_DIR(~/.omniroute/logs/application/app.log) instead ofprocess.cwd()(#6197) — the globally-installed CLI runs from an arbitrary working directory, so anchoring the default to cwd made file logging silently write to (or no-op under) an unrelated directory, contradicting the documented.env.exampledefault.getAppLogFilePath()now computes the default lazily via the pureresolveDataDir()resolver (honours a per-processDATA_DIR, no directory-creation side effect); an explicitAPP_LOG_FILE_PATHstill wins. Regression guard:tests/unit/logenv-datadir-path-6197.test.ts(3). (root cause independently diagnosed by @subhansh-dev in #6298 — thanks!) -
fix(docker): AgentBridge/
startMitmno longer aborts in containers/headless when the Antigravity-default DNS step can't write/etc/hosts(#6127), and the privileged command's stderr now reachesapp.loginstead of only a bare exit code hitting the toast (#6198). The default DNS step (addDNSEntry) was called unguarded while cert install and the two sibling DNS steps were each best-effort — in the runtime Docker image (USER node, nosudo, read-only/etc/hosts) it threwCommand failed with code 1out ofstartMitmInternaland killed the whole start, discarding the stderr. The three DNS steps are extracted into a best-effortprovisionDnsEntries()where each failure is logged with the fullerr(stderr included, folded in bysystemCommands.ts) and never aborts the start. Regression guard:tests/unit/mitm-dns-graceful-degrade-6127.test.ts(4). -
fix(providers): copilot-m365-web now supports the M365 Education "Starter / OfficeWebIncludedCopilot" tier and no longer returns an empty
content:nullstream (#6210). Two gaps: (1)buildWsUrl()hardcoded the individual-consumer scenario (OfficeWebPaidConsumerCopilot,isEdu=false) — the EDU tier is now opt-in viaproviderSpecificData.tier="edu", emittingscenario=OfficeWebIncludedCopilot/isEdu=true(the individual path is unchanged); (2) the EDU/GPT-5.5 path streams deltas viaarguments[0].writeAtCursor(incremental) instead of onlymessages[].text(accumulated snapshots), which the parser dropped — a newaccumulateBotContent()folds both formats, withtype:2 item.result.messageas a last-resort fallback. Regression guard:tests/unit/copilot-m365-edu-writeatcursor-6210.test.ts(10). (thanks @qpeyba) -
fix(providers): GitLab Duo executor now feeds tool results back into the prompt instead of looping (#6220) —
buildPrompt()branched only onsystem/userand tookuserParts.at(-1), silently dropping theassistant{tool_calls}+tool{result}turns the client appended, so the reconstructed prompt was byte-identical to turn 1 and the model re-emitted the same<tool>call forever. When a tool exchange is present the full conversation is now serialized, folding each tool result back keyed by itstool_call_id; simple conversations keep the legacy shape. Complements the tool_call emission from #6051 (thekilo-duplicatelabel was a false positive — different, sequential defect). Regression guard:tests/unit/gitlab-tool-result-feedback-6220.test.ts(4). -
fix(providers): opencode-go/opencode-zen can now synthesize the OpenCode CLI identity headers Cloudflare requires on VPS egress (#5997) — on a datacenter VPS,
opencode.ai/zen/go/v1/chat/completions403s (HTML challenge) requests lacking CLI identity, while the reporter's control curl proved thatUser-Agent: opencode-cli/1.0.0+x-opencode-client: cli+x-opencode-project: default+ fresh request/session UUIDs succeed. Opt-in viaOPENCODE_SYNTHESIZE_CLI_HEADERS=true(values overridable viaOPENCODE_GO_USER_AGENT/OPENCODE_USER_AGENT/OPENCODE_CLIENT/OPENCODE_PROJECT); it fills only headers the client did not already send. Kept off by default — the forward-only path is deliberate (fabricating a wrong value risks upstream rejection; a prior dedup regressed withopencode/local), so this replaces the fragile local header-injection shim without changing default behavior. Regression guard:tests/unit/opencode-cli-headers-synthesis-5997.test.ts(6). (thanks @aleksesipenko) -
fix(resilience): sticky session affinity now evicts and fails over to another account when the pinned account is exhausted/unavailable (#6219)
-
fix(sse): Responses API passthrough now drops internal commentary-phase output before forwarding to clients (gated by RESPONSES_PASSTHROUGH_DROP_COMMENTARY, default on) (#6199)
-
fix(proxy): stop the v3.8.44 proxy regression that leaked the real IP and disabled healthy proxies (#6246). Two coupled defects from the new health scheduler: (1) IP leak — when a proxy assigned to a connection was marked
inactive, resolution fell through to a direct egress instead of blocking, exposing the operator's real IP; (2) over-deactivation — the sweep flipped a proxy toinactiveon the first failed probe and counted our own 5s timeout / a probe-target5xxas the proxy's fault, so healthy paid proxies vanished from egress selection ("my proxies are not being used anymore"). Fix: the sweep decision is extracted into a pure, network-freedecideProxyHealthAction(src/lib/proxyHealth/decision.ts) — by default the health check now only counts/logs and never downgrades status (a proxy is downgraded/removed only withPROXY_AUTO_REMOVE=true, afterPROXY_AUTO_REMOVE_AFTERconsecutive conclusive failures); probes are classified tri-state so an inconclusive result (our timeout, or a5xxfrom the probe target) never penalizes the proxy, and the probe timeout is raised 5s→15s. Separately,safeResolveProxynow fails closed via the existing policy: a connection whose assigned proxy is dead is blocked instead of leaking direct (hasBlockingProxyAssignment), honoring the explicitproxy offtoggles and thePROXY_FAIL_OPEN=trueopt-out. Existing proxies stuckinactiveby the old behavior need a one-time manual re-activate (the operator owns proxy status). Regression guards:tests/unit/proxy-health-decide-action-6246.test.ts,tests/unit/proxy-assigned-unavailable-6246.test.ts. -
fix(proxy): make "Test All" read-only and add bulk enable/disable (#6246). Complements the core fail-closed / scheduler fix (#6296) with the two remaining reporter asks. (1) The "Test All" button (
POST /api/settings/proxies/auto-test) used to flip a proxy toinactiveon a failed reachability probe; since the egress selector excludesinactiveproxies, a flaky probe (an unreachablehttpbin.org, a proxy that blocksHEAD, or a slow paid proxy) silently disabled every proxy that failed — "Test All" is now read-only by default (only the operator sets a proxy active/inactive; opt back into the legacy test-and-set withPROXY_HEALTH_AUTO_DEACTIVATE=true). (2) Adds a bulk enable/disable proxies endpoint + toolbar action (POST /api/settings/proxies/batch-activate) so an operator can re-activate proxies in one click. Regression guard:tests/unit/proxy-health-6246.test.ts. (thanks @tenshiak) -
chatcore (tools): stop the default 128-tool cap from silently dropping opencode's
task/MCP tools. opencode (used as an MCP/agent host) sends a large tool list; when it exceeds the speculativeMAX_TOOLS_LIMIT(128) default,truncateToolListdid a blindtools.slice(0, 128), dropping every tool past index 128 — including opencode's built-intasktool (subagent launch) and many MCP tools, so models routed through OmniRoute could no longer spawn subagents or reach part of their tools. The cap exists to avoid upstream400s for providers with real hard limits (e.g. grok-cli 200), so it is kept for those: detection of the opencode client (isOpencodeClient— anyx-opencode-*header, oropencodein the user-agent) now only bypasses the speculative 128 default, never a known provider ceiling. Precedence is explicit — a proactive/detected provider limit always truncates (even for opencode); otherwise opencode forwards its full tool list; otherwise the unchanged 128 default applies to every other client. RefactorsgetEffectiveToolLimitintogetKnownToolLimit(provider) ?? DEFAULT_LIMIT(byte-identical for existing callers) and fixes a cosmetic debug-log that reported the truncated count instead of the original. Regression guard:tests/unit/tool-limit-detector.test.ts. (#6193 — thanks @DKotsyuba) -
fix(mitm): the macOS MITM-cert install check now matches the system keychain again.
security find-certificate -a -Zprints the SHA-1 as a colon-less hex string, but the installed-check compared it againstgetCertFingerprint()'s colon-separated form, so the substring match never hit — the cert was reported as not-installed and re-prompted for the sudo install on every run. Fingerprints are now normalized (colons stripped, upper-cased) on both sides via the extractedmacCertOutputHasFingerprinthelper. Regression guard:tests/unit/mitm-cert-mac-fingerprint.test.ts. (#6204, closes #6134 — thanks @rianonehub) -
fix(api):
/v1/messages/count_tokensnow countstool_use,tool_resultandthinkingcontent blocks (and array-formsystemprompts) in the local-estimation path, instead of onlytext. Real agentic conversations keep ~95% of their tokens inside tool results; the previous estimate returned near-zero for them, which silently broke Claude Code's auto-compaction (context grew past the window with no compaction until the upstream API rejected the request). The real provider-side count path is unchanged. Regression guard:tests/unit/messages-count-tokens-route.test.ts. (#6221 — thanks @luweiCN) -
fix(antigravity): strip a trailing assistant prefill turn for Vertex Claude models to avoid upstream 400s (#6114). Regression guard:
tests/unit/antigravity-claude-prefill-strip.test.ts. (thanks @anki1kr) -
fix(security): the mutable cloud-agent routes (
/api/cloud/credentials/update,/api/cloud/models/alias) now require management auth instead of being treated as public. They were classified as public API routes, so a request without management credentials could update stored cloud-agent credentials and model aliases. They are removed from the public-route set, classified as management routes in the authz pipeline, and gated byrequireManagementAuth; cloud read/auth routes stay public. Regression guards:tests/unit/cloud-write-auth.test.ts,tests/unit/authz/classify.test.ts,tests/unit/public-api-routes.test.ts. (#6233 — thanks @vittoroliveira-dev) -
refactor(dashboard): extract the onboarding-wizard "Open provider details" link target into a pure, unit-tested
buildProviderDetailsHref(connection)helper. The wizard already routes byconnection.id(the node UUID) rather than the provider category slug (#6144/#6145); this hardens that behavior behind a tested helper that guards a missing id/connection. Regression guard:tests/unit/provider-onboarding-href.test.ts. (#6166 — thanks @KooshaPari) -
fix(api): relay worker now binds the SSRF guard to a stable
constname so minified standalone (Docker) builds resolve it (#6149) — the Vercel/Deno relay generators embedded the sharedresolveRelayTargetguard as a bare${fn.toString()}declaration while the worker body called the hardcoded literal name; SWC minification mangled the source function's name, so the deployed worker defined<mangled>but still calledresolveRelayTarget→ReferenceError. Both templates now emitconst resolveRelayTarget = ${fn.toString()};(the const name is a template literal, immune to minification). Regression guard:tests/unit/relay-minified-fn-6149.test.ts(4). (thanks @SeaXen) -
fix(providers): refresh the stale NVIDIA NIM model registry — drop EOL
z-ai/glm-5.1, addz-ai/glm-5.2andnvidia/nemotron-3-ultra-550b-a55b(#6108). Regression guard:tests/unit/nvidia-nim-registry-6108.test.ts. (thanks @andrea-kingautomation) -
fix(backend): GPT-family (codex) models now report a distinct
max_input_tokens(272000) below their 400Kcontext_lengthvia an optionalmaxInputTokensonRegistryModel, so coding agents auto-compact correctly instead of overflowing the real input cap (#6191). Regression guard:tests/unit/gpt-max-input-tokens-6191.test.ts. (thanks @luweiCN) -
fix(backend): call logs now record a reasoning source/char-count (migration 116,
reasoning_source/reasoning_chars) for models that emitreasoning_content/<think>but report zero reasoning tokens in usage, sotokens_reasoningno longer silently under-represents reasoning — cost math is unchanged (the pricedtokens_reasoningstays usage-derived) (#6187). Regression guard:tests/unit/reasoning-token-source-6187.test.ts. (thanks @andrea-kingautomation) -
fix(auth): a stale/changed
STORAGE_ENCRYPTION_KEYnow surfaces as a clear 424storage_encryption_stale("re-enter the API key") instead of a misleading "Auth failed: 401" — the connection's ciphertext failed to decrypt and was coerced to an empty Bearer, hiding the real cause (#6148). Regression guard:tests/unit/decrypt-stale-key-hint-6148.test.ts. (thanks @chirag127) -
fix(backend): memory injection now keeps the injected system message first for providers that require it (via a
PROVIDERS_SYSTEM_MUST_BE_FIRSTcapability), instead of the cache-safe mid-array splice that made strict providers reject the request with a 400 (#6135). Regression guard:tests/unit/memory-system-first-6135.test.ts. -
fix(services): 9Router embed panel no longer 404s (optional catch-all route) and the supervisor probes the port before spawning to avoid raw EADDRINUSE (#6205). Regression guards:
tests/unit/ninerouter-embed-port-6205.test.ts,tests/unit/services/ServiceSupervisor.test.ts. (thanks @jonlwheat2-gif) -
fix(mcp): forward the MCP request
extracontext through static tool loops so stdio callers keep their scope/identity (#6178)
⚡ Performance & Infrastructure
- perf(test): test-suite loader quick wins (#6214) — the 19 test scripts switch
--import tsx→--import tsx/esm(the repo is pure ESM; the unused CJS hook cost ~1.3s per test process × 2,462 processes — CI fast-path unit shards dropped 14.8→7.5 min, −49%), tsx bumped to ^4.23.0 (tsx#809 startup-regression fix), 37 orphan.test.mjsfiles (224 cases) recovered into the canonical glob (they matched no runner and never ran in any CI job;check:test-discoverynow scans.mjstoo), and ci.yml/quality.yml unit jobs now call the canonical npm scripttest:unit:ci:shard(single source of truth — closes two silent drifts: missingsetupPolyfillimport in CI andmemory/+usage/dirs absent from the fast-path glob).tests/unit/dashboard/**keeps the full tsx hook in its own invocation (@lobehub/iconses/ build internallyrequire()s ESM-syntax files). - ci: heavy-pipeline dedup (#6215) — the release-PR pipeline ran the unit suite 4× per sync (95 jobs, 208 machine-min; the v3.8.44 cycle fired 123 such runs, 88 cancelled). Now: Node 24/26 compat matrices move to a daily
nightly-compat.yml(−28%/run; resolves the active release branch, opens a tracking issue on failure), coverage is collected inside the unit shards themselves via c8/NODE_V8_COVERAGE(−18%/run; the Coverage Shard ×8 matrix is gone — nodejs/node's own CI pattern), the ~40-job per-language i18n matrix becomes 1 job (the account has 20 concurrent-job slots total), and heavy jobs skip draft PRs — paired with/generate-releasenow opening the living release PR as draft (flipped ready at the new Phase 0a.0a), killing the per-merge churn for the whole cycle. Validated by a fullworkflow_dispatchof the new pipeline: 35 jobs, 0 failures, 23 min, merged coverage 80.16% (> ratchet baseline). - feat(quality): no-new-warnings per PR (#6218) — native ESLint bulk suppressions (≥9.24) freeze the pre-existing debt (476 files / 4,273 violations in
config/quality/eslint-suppressions.json);npm run lint, lint-staged (pre-commit) and a new fork-awarelint-guardjob in quality.yml all run suppressions-aware, so a NEW warning goes red in the PR that introduces it instead of accruing invisibly (+41/+88 per cycle) and being blind-rebaselined at release. 3 warn rules promoted to error insrc/**(react-hooks/exhaustive-deps,@next/next/no-img-element,import/no-anonymous-default-export);collect-metricsmeasures under the frozen baseline (ratchet metric = net-NEW debt; baseline tightened 4,279→0 in-PR per require-tighten); fork PRs run report-only (contributors are never blocked — the maintainer campaigns fix via co-authorship). Baseline stock shrinks via--prune-suppressionsat release reconciliation. - ci: test jobs no longer wait on the Build gate (#6275) —
test-unit×8,vitest,integration×2 andsecuritydeclaredneeds: buildbut never download thenext-buildartifact; they now start at minute 0 (needs: changes, sameifas Build), cutting ~15–20 min of wall-clock per heavy run.e2e/package-artifact/electron-smokekeepneeds: build(they consume the artifact for real). - ci(build): the ci.yml Build job compiles Next.js with Turbopack (
OMNIROUTE_USE_TURBOPACK=1) (#6273) — Build job 20 min → 6 min 59 s (~2.9×) on ubuntu-latest; the webpackactions/cachestep is removed. Validated end-to-end pre-merge viagh workflow run ci.yml --ref <branch>. - feat(build): Turbopack becomes the default bundler for
next buildandnext dev(#6283) —build-next-isolated.mjs,run-next.mjsand the playwright-runner default to Turbopack;OMNIROUTE_USE_TURBOPACK=0is the explicit webpack escape hatch.nightly-compat.yml/npm-publish.ymlinherit the default. Regression guard:tests/unit/build-bundler-default-turbopack.test.ts. - feat(docker): the Docker image builds with Turbopack (
ENV OMNIROUTE_USE_TURBOPACK=1) (#6285) — the v3.8.27 ImportTracer panic ("unreachable: there must be a path to a root") does not reproduce on Next 16.2.9: amd64 (659 s) and arm64 (qemu) build clean, 0 panics, smoke health 200. - ci: opt-in self-hosted VPS runners for the release window (#6284) —
scripts/vps/release-runner-up.sh/down.shmanage the runner VM, andbuild/test-unit/vitestpick a dynamicruns-ongated byvars.USE_VPS_RUNNER == 'true'and own-origin (fork PRs never reach self-hosted runners). Wired into/generate-release(VM up at Phase 1, mandatory down at Phase 3).
📝 Maintenance
- quality(release-green): full pre-flight hardening for this release — the cycle's 11 net-new ESLint errors typed/fixed and
validate-release-greenmade suppressions-aware with per-gate logs (_artifacts/release-green/) and a--hermeticmode; test-masking allowlist entries for the cycle's verified-legitimate assert reductions; stale ESLint suppressions pruned (4,273 → 4,233); the 7 net-newas anycasts from #6292 typed;githubSkillToolsMCP errors routed throughsanitizeErrorMessage();combo-provider-cooldown-siblingadded to the Stryker tap set; executors/env docs count fixes. - ci(quality): merge-integrity fast-gates per PR —
check:changelog-integrity(no base CHANGELOG bullet may vanish in the merge result — the auto-resolve "CHANGELOG-eat" pattern) andcheck:agent-skills-sync(generated SKILL.md ≡ catalog), blocking for own-origin branches and report-only for forks (Princípio Zero). (#6300) - ci(vps): hermetic
nightly-release-greenpre-flight on the dedicatedomni-releaseself-hosted runner (dynamicruns-on, clean env); e2e/integration/electron stay on hosted runners (per-VM port collision + concurrent artifact-download limits documented in the PR). (#6305) - chore(quality): v3.8.45 cycle-close drift rebaselines — file-size (13 files grown by merged cycle PRs), cognitive 867→877, cyclomatic 2028→2035, kiro-translator test debt from #6213; all with dated justification keys.
- docs(architecture): sync stale DB-layer counts (45+/55 → 95+/110+) in REPOSITORY_MAP, the db-schema diagram and llm.txt (+42 i18n mirrors). (#6167)
- chore(release): parallel-cycle flow —
sync-next-cycle.mjs+ Hard Rule #21 semantics (#6203); v3.8.45 development cycle opened. - i18n(it): add 118 missing Italian (
it) translations (net-additive — no existing keys dropped, valid JSON), improving Italian UI coverage. (#6212 — thanks @serverless83) - chore(providers): remove deprecated MiMo V2 model entries from the catalogs (xiaomi-mimo, opencode-go, zenmux-free, audio TTS) — the upstream V2 line is superseded by MiMo V2.5; drops
mimo-v2-tts,mimo-v2-pro,mimo-v2-omni,mimo-v2-flash,mimo-v2-flash-freeand realigns the provider-catalog tests. (#6248 — thanks @backryun)
🙌 Contributors
Thanks to everyone whose work landed in v3.8.45:
| Contributor | PRs / Issues |
|---|---|
| @aleksesipenko | direct commit / report |
| @andrea-kingautomation | direct commit / report |
| @anki1kr | #6078 |
| @arssnndr | #6162, #6163 |
| @backryun | #6154, #6235, #6248 |
| @chirag127 | direct commit / report |
| @DKotsyuba | #6193 |
| @hartmark | #6216 |
| @Iammilansoni | #6150, #6200, #6209, #6245 |
| @jonlwheat2-gif | direct commit / report |
| @kanztu | #6181 |
| @karimalsalah | #6291 |
| @KooshaPari | #6166 |
| @LuisAlejandroVega | #6177 |
| @luweiCN | #6221 |
| @Moseyuh333 | #6186 |
| @muflifadla38 | direct commit / report |
| @powellnorma | direct commit / report |
| @qpeyba | direct commit / report |
| @RCrushMe | direct commit / report |
| @rianonehub | #6134, #6204 |
| @SeaXen | direct commit / report |
| @serverless83 | #6212 |
| @subhansh-dev | #6298 (diagnosis, landed via #6234) |
| @tenshiak | direct commit / report |
| @Theadd | #6194, #6195 |
| @vittoroliveira-dev | #6233 |
| @VXNCXNX | #6213 |
| @diegosouzapw | maintainer |
What's Changed
- Release v3.8.45 by @diegosouzapw in #6202
Full Changelog: v3.8.44...v3.8.45