diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 3c641330f1..0913d78cd0 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -24,6 +24,15 @@ updates:
update-types: ["version-update:semver-major"]
- dependency-name: "eslint-config-next"
update-types: ["version-update:semver-major"]
+ # typescript majors are peer-blocked by typescript-eslint, which pins a hard
+ # upper bound (8.64.0 → peerDependencies.typescript ">=4.8.4 <6.1.0"). A TS 7
+ # bump therefore violates the peer and takes down the whole toolchain at once —
+ # #7068 grouped it with 6 harmless bumps and turned Build + Lint + Quality Ratchet
+ # + Unit (6/8, 8/8) + Integration (1/2, 2/2) + dast-smoke red in one shot, blocking
+ # the innocuous updates riding along with it. Un-ignore once typescript-eslint
+ # widens the peer, and migrate TS majors intentionally (own PR, own CI run).
+ - dependency-name: "typescript"
+ update-types: ["version-update:semver-major"]
# jscpd v5 is a Rust rewrite (native binary, no Node.js programmatic API).
# scripts/check/check-duplication.mjs is deliberately pinned to jscpd@4 (it
# parses jscpd-report.json against a frozen baseline). A v5 major would break
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index df99b771ae..d97161e470 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -519,7 +519,7 @@ jobs:
with:
node-version: ${{ env.CI_NODE_VERSION }}
- name: Fetch base branch
- run: git fetch --no-tags origin "${GITHUB_BASE_REF}" --depth=1
+ run: git fetch --no-tags origin "${GITHUB_BASE_REF}"
- name: Validate source changes include tests
run: node scripts/check/check-pr-test-policy.mjs --summary-file .artifacts/pr-test-policy.md
# Anti test-masking: flag net assert removal / new assert.ok(true) in changed tests.
@@ -670,10 +670,16 @@ jobs:
if: runner.os == 'Linux'
working-directory: electron
run: npm run pack
+ # ADVISORY while the new Windows leg matures (repo convention, dast-smoke
+ # precedent): its first-ever real run (2026-07-15, run 29457533565) died in
+ # 0.7s with the error swallowed by pwsh — bash shell captures stderr and
+ # continue-on-error keeps the heavy gate green while we harden it (#7336).
- name: Prepare Electron standalone (Windows ABI rebuild + spawn path)
if: runner.os == 'Windows'
working-directory: electron
- run: npm run prepare:bundle
+ continue-on-error: true
+ shell: bash
+ run: npm run prepare:bundle 2>&1
- name: Smoke packaged Electron app
if: runner.os == 'Linux'
env:
@@ -783,7 +789,10 @@ jobs:
test-coverage:
name: Coverage
runs-on: ubuntu-latest
- timeout-minutes: 10
+ # 10min was sized before #7114 added the lcov reporter (Codecov/Sonar need it);
+ # merging 8 shard JSONs + text+json+lcov now takes ~10-12min — three consecutive
+ # release-tip runs died at exactly 10m as job-timeout "cancelled" (2026-07-15/16).
+ timeout-minutes: 20
needs: test-unit
if: ${{ !cancelled() && needs.test-unit.result == 'success' && !contains(github.event.pull_request.labels.*.name, 'hotfix') }}
env:
diff --git a/.github/workflows/nightly-release-green.yml b/.github/workflows/nightly-release-green.yml
index 9d1d20b565..63d1b4a054 100644
--- a/.github/workflows/nightly-release-green.yml
+++ b/.github/workflows/nightly-release-green.yml
@@ -22,7 +22,7 @@ name: Release-Green (continuous)
on:
push:
- branches: ["release/v*"]
+ branches: ["release/v*", "main"]
paths:
- "src/**"
- "open-sse/**"
@@ -61,6 +61,9 @@ env:
jobs:
release-green:
name: Validate active release branch
+ # On a push, only run for release/* pushes — a push to main is handled by the
+ # main-green job below. Schedule/dispatch always run (they validate the highest release).
+ if: ${{ github.event_name != 'push' || startsWith(github.ref_name, 'release/') }}
# Dynamic runner: with USE_VPS_RUNNER=true (release window / on-demand pre-flight)
# this runs on the dedicated VPS runner — clean env (no operator OMNIROUTE_API_KEY,
# no local noauth CLIs => zero machine-specific false positives) and no contention.
@@ -201,3 +204,100 @@ jobs:
release-green.json
release-green.log
if-no-files-found: ignore
+
+ # Companion arm for `main`. Under the parallel-cycle model, main only receives merged
+ # work at the release squash — so a gate/infra fix that lands only on release leaves
+ # main red the whole cycle, and repo-wide gates (CodeQL alert count, ratchet baselines)
+ # turn EVERY PR into main red on a check unrelated to its diff. This detects that and
+ # opens a "🔴 main not green" tracking issue. The PREVENTION is the companion-PR reflex
+ # (Hard Rule #21 area / _shared/merge-gates.md §8); this is the automated backstop.
+ main-green:
+ name: Validate main branch
+ # On a push, only run for a push to main — a push to release/* is handled by
+ # release-green above. Schedule/dispatch always run (they also sweep main).
+ if: ${{ github.event_name != 'push' || github.ref_name == 'main' }}
+ runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-latest' }}
+ env:
+ JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation
+ API_KEY_SECRET: ci-nightly-api-key-secret-long
+ DISABLE_SQLITE_AUTO_BACKUP: "true"
+ steps:
+ - uses: actions/checkout@v7
+ with:
+ ref: main # literal — no injection surface; scheduled runs default to the repo default branch (a release/v*), so pin main explicitly
+ fetch-depth: 0
+ persist-credentials: false
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: "24"
+ cache: npm
+
+ - uses: ./.github/actions/npm-ci-retry
+
+ - name: Main-green validation
+ id: validate
+ env:
+ EVENT_NAME: ${{ github.event_name }}
+ run: |
+ set +e
+ # push (a merge into main) → --quick fast HARD gates; schedule/dispatch → full sweep.
+ if [ "$EVENT_NAME" = "push" ]; then
+ MODE="--quick"
+ else
+ MODE="--with-build --full-ci"
+ fi
+ echo "[main-green] mode: $MODE (event: $EVENT_NAME)"
+ # shellcheck disable=SC2086 — MODE is an intentional flag list
+ node scripts/quality/validate-release-green.mjs --json --hermetic $MODE \
+ 1> main-green.json 2> main-green.log
+ echo "exit=$?" >> "$GITHUB_OUTPUT"
+ echo "------- report -------"
+ cat main-green.log
+
+ - name: Open / update tracking issue on HARD failure
+ if: steps.validate.outputs.exit != '0'
+ env:
+ GH_TOKEN: ${{ github.token }}
+ RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ EVENT_NAME: ${{ github.event_name }}
+ run: |
+ set -euo pipefail
+ TITLE="🔴 main branch not green"
+ {
+ echo "The **main-green** validation found HARD failures on \`main\`."
+ echo ""
+ echo "Because \`main\` only receives merged work at the release squash, a gate/infra"
+ echo "fix that landed only on the release branch leaves \`main\` broken for the whole"
+ echo "cycle — and repo-wide gates (CodeQL alert count, ratchet baselines) then turn"
+ echo "**every open PR into main** red on a check unrelated to its diff. The fix is a"
+ echo "companion PR \`--base main\` carrying the release-side fix (see"
+ echo "\`_shared/merge-gates.md\` §8), NOT chasing each contributor PR."
+ echo ""
+ echo "**Run:** ${RUN_URL} (mode: ${EVENT_NAME})"
+ echo ""
+ echo '```'
+ sed -n '/──────── verdict ────────/,$p' main-green.log || tail -40 main-green.log
+ echo '```'
+ echo ""
+ echo "_Ratchet drift (eslint warnings / cognitive-complexity / file-size) is expected mid-cycle and did NOT, on its own, open this issue._"
+ } > issue-body.md
+
+ EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
+ --search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "")
+ if [ -n "$EXISTING" ]; then
+ gh issue comment "$EXISTING" --repo "$GITHUB_REPOSITORY" --body-file issue-body.md
+ echo "Updated existing issue #$EXISTING"
+ else
+ gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" --body-file issue-body.md
+ fi
+
+ - name: Upload report artifact
+ if: always()
+ uses: actions/upload-artifact@v7
+ with:
+ name: main-green-report
+ path: |
+ main-green.json
+ main-green.log
+ if-no-files-found: ignore
diff --git a/AGENTS.md b/AGENTS.md
index 42209a90d0..92eb830425 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -3,12 +3,12 @@
## Project
Unified AI proxy/router — route any LLM through one endpoint. Multi-provider support
-with **250 provider entries** (OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Fireworks,
+with **251 provider entries** (OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Fireworks,
Cohere, NVIDIA, Cerebras, Pollinations, Puter, Cloudflare AI, HuggingFace, DeepInfra,
SambaNova, Meta Llama API, Moonshot AI, AI21 Labs, Databricks, Snowflake, and many more)
with **MCP Server** (94 tools), **A2A v0.3 Protocol**, and **Electron desktop app**.
-> **Live counts (v3.8.47)**: providers 250 · MCP tools 94 · MCP scopes 30 · A2A skills 6 ·
+> **Live counts (v3.8.49)**: providers 251 · MCP tools 94 · MCP scopes 30 · A2A skills 6 ·
> open-sse services 134 · routing strategies 17 · auto-combo scoring factors 12 ·
> DB modules 95 · DB migrations 110 · base tables 17 · search providers 11 ·
> i18n locales 42. **Refresh with `npm run check:docs-all`.**
diff --git a/CLAUDE.md b/CLAUDE.md
index 65100f4fc4..5427c24779 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -35,7 +35,7 @@ For full test matrix, see `CONTRIBUTING.md` → "Running Tests". For deep archit
## Project at a Glance
-**OmniRoute** — unified AI proxy/router. One endpoint, 250 LLM providers, auto-fallback.
+**OmniRoute** — unified AI proxy/router. One endpoint, 251 LLM providers, auto-fallback.
| Layer | Location | Purpose |
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
diff --git a/Dockerfile b/Dockerfile
index adf3cf5e91..98e0a3215b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -114,6 +114,12 @@ LABEL org.opencontainers.image.title="omniroute" \
ENV NODE_ENV=production
ENV PORT=20128
ENV HOSTNAME=0.0.0.0
+# Runtime heap ceiling. 1024MB is enough for normal traffic but can be tight
+# for large fusion-combo panels (many models fanned out in parallel, each
+# response buffered in full — see open-sse/services/fusion.ts::FUSION_DEFAULTS
+# .maxPanel, issue #1905). Override at `docker run` time with
+# `-e OMNIROUTE_MEMORY_MB=2048` (or higher) if you raise fusionTuning.maxPanel
+# above the default cap.
ENV OMNIROUTE_MEMORY_MB=1024
ENV NODE_OPTIONS="--max-old-space-size=${OMNIROUTE_MEMORY_MB}"
diff --git a/README.md b/README.md
index 0ccfa90a47..d19b353f43 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
# 🚀 OmniRoute — The Free AI Gateway
-### Never stop coding. Connect every AI tool to **250 providers** — **90+ free** — through one endpoint.
+### Never stop coding. Connect every AI tool to **251 providers** — **90+ free** — through one endpoint.
**Plug Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini. Auto-fallback.**
@@ -149,11 +149,11 @@
-> One endpoint. **250 providers.** Never stop building — and let OmniRoute pick the cheapest one that works.
+> One endpoint. **251 providers.** Never stop building — and let OmniRoute pick the cheapest one that works.