Files
OmniRoute/docs/ops/RELEASE_CHECKLIST.md
Diego Rodrigues de Sa e Souza cadc3f10b7 Release v3.8.35 (#4743)
* chore(release): open v3.8.35 development cycle

* fix db vacuum scheduler settings (#4726)

Scheduled VACUUM now follows Storage page settings (scheduledVacuum/vacuumHour) as single source of truth; env-flag control path removed. 11/11 vacuum-scheduler tests pass against release/v3.8.35 tip; no orphaned env refs. Integrated into release/v3.8.35.

* fix(tier): noAuth providers count as free; free filter returns empty … (#4753)

noAuth providers now classified free (union of legacy list + NOAUTH_PROVIDERS chat-tier derivation), -free arena_elo alias, and auto/<cat>:free returns an empty pool when no free candidate matches (opt-in legacy fallback via OMNIROUTE_AUTO_FREE_FALLBACK_TO_FULL_POOL). New env var documented in .env.example + ENVIRONMENT.md; CHANGELOG bullet added (maintainer co-author). 46/46 node + 56/56 vitest tests pass on release tip; env-doc-sync, docs-sync, typecheck:core, lint, file-size all green. Integrated into release/v3.8.35.

* refactor(chatCore): extrai 11 helpers de nível superior para 6 leaves puros (#3501) (#4571)

chatCore god-file decomposition (#3501): extract 6 pure leaves (cacheUsageMeta, executorClientHeaders, nonStreamingResponseBody, skillsFormat, streamErrorResult, streamFinalize) from chatCore.ts. Rebased onto release/v3.8.35 tip (resolved single chatCore.ts conflict — removed now-extracted inline buildExecutorClientHeaders). 265/265 chatcore tests, 26/26 new leaf tests, typecheck:core, cycles, file-size all green. Integrated into release/v3.8.35.

* refactor(chatCore): extrai resolveExecutorWithProxy + getExecutionCredentials para leaves (#3501) (#4646)

chatCore #3501: extract resolveExecutorWithProxy + getExecutionCredentials to leaves (executorProxy.ts, executionCredentials.ts). Clean cherry-pick onto release tip post-#4571. 12/12 new leaf tests, typecheck:core, cycles, file-size green. Integrated into release/v3.8.35.

* refactor(chatCore): extrai transforms de mensagens Claude p/ leaf (#3501) (#4708)

chatCore #3501: extract Claude upstream-message transforms to leaf (claudeUpstreamMessages.ts + claudeMessageTypes.ts). Clean cherry-pick post-#4646. 8/8 new leaf tests, typecheck/cycles/file-size green. Integrated into release/v3.8.35.

* refactor(chatCore): extrai persistAttemptLogs para leaf (#3501) (#4717)

chatCore #3501: extract persistAttemptLogs to leaf (attemptLogging.ts). Rebased onto release tip post-#4708 (resolved imports conflict: kept tip's resolveCompressionHeader from compression Phase 3, dropped now-unused logTruncation import moved into the leaf). 288/288 chatcore tests, typecheck/cycles/file-size green. Integrated into release/v3.8.35.

* refactor(chatCore): extrai stageTrace + compressionUsageReceipt para leaves (#3501) (#4721)

chatCore #3501: extract stageTrace + compressionUsageReceipt to leaves. Clean cherry-pick post-#4717. 6/6 new leaf tests, typecheck/cycles/file-size green. Integrated into release/v3.8.35.

* refactor(chatCore): extrai prepareUpstreamBody (1ª sub-fatia do executeProviderRequest, #3501) (#4730)

chatCore #3501: extract prepareUpstreamBody (first sub-slice of executeProviderRequest) to leaf (upstreamBody.ts). Clean cherry-pick post-#4721. 7/7 new leaf tests, full 301/301 chatcore suite, typecheck/cycles/file-size green. Completes the 6-PR chatCore decomposition stack into release/v3.8.35.

* fix(db): make db-backup import size cap configurable (#4719) (#4757)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* chore(quality): expand check:release-green to the FULL release-PR gate set (#4758)

The release-green pre-flight (Solution C) previously covered only a subset of the
gates that run exclusively on the release PR (PR→main), so reds still accrued
silently on release/** and surfaced in ~40-min layers at release time (v3.8.34:
3 CI rounds — CodeQL sanitization, then the fail-fast Quality Ratchet revealing
openapi then cyclomatic-complexity one push at a time, plus zizmor/integration).

Now check:release-green reproduces the COMPLETE release-PR gate set and reports
EVERY red in one pass (collected, not fail-fast):

- New DRIFT ratchets (report-only, rebaselined at release, never block):
  cyclomatic complexity, dead-code, type-coverage, compression-budget,
  openapi-coverage, workflow-lint (zizmor), codeql-ratchet.
- New HARD gates (real defects): docs-all (fabricated-docs strict + i18n mirror
  sync) and the integration test suite (gated behind !--quick).

The only release-PR gates it still cannot reproduce locally are GitHub-side CodeQL
semantic analysis and SonarQube/SonarCloud (external services).

The nightly-release-green workflow and /green-prs inherit the expanded coverage
automatically (they invoke this script), so cycle drift is now surfaced
continuously and the release PR is green on its first CI run.

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(dashboard): add missing onboarding.tiers step title (#4698) (#4755)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* feat(compression): Output Styles registry + D0 telemetry (Phase 4A) (#4694)

Phase 4A: Output Styles registry + D0 telemetry. Integrated into release/v3.8.35.

* feat(compression): SLM tier for ultra (Phase 4B) [stacked on #4694] (#4707)

Phase 4B: SLM tier for ultra. Integrated into release/v3.8.35.

* feat(compression): context-budget adaptive compression (Phase 4C) [stacked on #4707] (#4716)

Phase 4C: adaptive context-budget compression. Integrated into release/v3.8.35.

* feat(compression): offline evaluation harness (Phase 4 D1) [stacked on #4716] (#4720)

Phase 4 D1: offline evaluation harness. Integrated into release/v3.8.35.

* fix(sse): deepseek-web folds role:tool results into prompt transcript (#4712) (#4756)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(dashboard): remove dead unconditional useLiveRequests call in HomePageClient (#4759, #4745, #4596) (#4761)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* fix(dashboard): dedupe provider nodes by id on compatible-provider add (#4746) (#4768)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* chore(db): re-export compressionRunTelemetry from localDb to satisfy db-rules (#4775)

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>

* docs(security): add canonical STRIDE-based threat model (#4783)

Canonical STRIDE threat model. Integrated into release/v3.8.35.

* test(dashboard): add smoke test for home client dashboard (#4793)

Smoke test guarding the dashboard home client render (regression #4745/#4759). Code fix already landed via #4761; this PR's jsdom smoke test is the net-new regression guard. Integrated into release/v3.8.35.

* fix(combos): auto-promote zeroLatencyOptimizationsEnabled so legacy configs (pre-3.8.33 fallbackCompressionMode="lite") round-trip on the first GUI edit (#4774)

Auto-promote zeroLatencyOptimizationsEnabled + strip v3.8.31-era removed keys so legacy combo configs round-trip through PUT /api/combos/{id} on first GUI edit (closes #4382 followup). Pre-merge: rewrote the now-stale reject test to assert auto-promotion + added passthrough/round-trip regression guards; reconciled combos/page.tsx file-size baseline. Integrated into release/v3.8.35.

* refactor(chatCore): extrai parse + usage-stats não-streaming do executeProviderRequest (#3501) (#4762)

chatCore #3501: extract parseNonStreamingResponseBody + recordNonStreamingUsageStats. Integrated into release/v3.8.35.

* refactor(chatCore): extrai recordContextEditingTelemetryHook (#3501) (#4779)

chatCore #3501: extract recordContextEditingTelemetryHook. Integrated into release/v3.8.35.

* refactor(chatCore): extrai recordCompressionCacheStats (#3501) (#4792)

chatCore #3501: extract recordCompressionCacheStats. Integrated into release/v3.8.35.

* refactor(chatCore): extrai writeCavemanOutputAnalytics (#3501) (#4794)

chatCore #3501: extract writeCavemanOutputAnalytics. Integrated into release/v3.8.35.

* refactor(chatCore): extrai scheduleQuotaShareConsumption (POST-hook não-streaming, #3501) (#4780)

chatCore #3501: extract scheduleQuotaShareConsumption (non-streaming POST-hook). Integrated into release/v3.8.35.

* refactor(chatCore): extrai emitRequestGamificationEvent (helper compartilhado DRY, #3501) (#4776)

chatCore #3501: extract emitRequestGamificationEvent (DRY streaming/non-streaming). Integrated into release/v3.8.35.

* refactor(chatCore): extrai runPluginOnResponseHook (#3501) (#4782)

chatCore #3501: extract runPluginOnResponseHook. Integrated into release/v3.8.35.

* refactor(chatCore): extrai scheduleStreamingQuotaShareConsumption (POST-hook streaming, #3501) (#4784)

chatCore #3501: extract scheduleStreamingQuotaShareConsumption (streaming POST-hook). Integrated into release/v3.8.35.

* refactor(chatCore): extrai recordStreamingUsageStats (analytics de usage streaming, #3501) (#4791)

chatCore #3501: extract recordStreamingUsageStats. Integrated into release/v3.8.35.

* refactor(chatCore): extrai recordStreamingCost (custo por-request streaming, #3501) (#4790)

chatCore #3501: extract recordStreamingCost (per-request streaming cost). Integrated into release/v3.8.35.

* docs(readme): credit ponytail + OmniCompress; restore env-doc-sync release-green (#4799)

README compression credits (ponytail/OmniCompress) + env-doc-sync ignore for eval-only OMNIROUTE_EVAL_CREDENTIALS (restores release-green after #4720). Integrated into release/v3.8.35.

* chore(quality): trim combo-config.test.ts comments under file-size cap (#4774 follow-up) (#4800)

Restore file-size release-green. Integrated into release/v3.8.35.

* feat(api-docs): Redoc-rendered /api/docs + consolidate OpenAPI spec to docs/openapi.yaml (#4781)

Redoc /api/docs + OpenAPI spec consolidated to docs/openapi.yaml (canonical 201-path complete spec; old path → legacy fallback). All refs/gates/tests/CI updated. Integrated into release/v3.8.35.

* docs(compression): declare Phase 4 layers — Output Styles, adaptive dial, per-request control (#4801)

The README compression section listed the 9 input engines but not the Phase 4
layers now in production:
- Output Styles (output-axis steering: terse-prose / less-code / terse-cjk, lite/full/ultra)
- adaptive context-budget dial (reserve-output|percentage|absolute · floor|replace-autotrigger|off)
- per-request x-omniroute-compression precedence + the offline eval harness
Also bumped the highlights range to v3.8.35, expanded the compression feature bullet,
and marked the GUIDE's Phase 4 row Shipped (was 'Planned' — it's merged on v3.8.35).

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(release): finalize v3.8.35 CHANGELOG + docs reconciliation

- CHANGELOG: complete 3.8.35 section (all 35 commits since v3.8.34,
  contributor attribution: @rdself @megamen32 @KooshaPari @JxnLexn)
- docs(security): align THREAT_MODEL.md refs with real code
  (routeGuard.ts, tokenLimits.ts, /api/monitoring/health) — fabricated-docs gate
- check:fabricated-docs: skip docs/superpowers/specs (dated research reports)
- i18n: sync 3.8.35 section into 41 CHANGELOG mirrors (docs-sync size gate)
- ratchet rebaseline: cyclomatic 1916->1920, eslintWarnings 3907->3912
  (inherited cycle drift; release-finalize diff is docs-only)

* fix(release): resolve inherited base-reds surfaced by v3.8.35 release CI

Cycle base-reds that only run on PR→main (not the PR→release fast-path):

- test(autoCombo): suffixComposition-4517 used node:test in a vitest-only dir
  (#4753) → vitest found no suite. Switch to the vitest API. (Vitest job)
- test(agentSkills): openapiParser fixture wrote docs/reference/openapi.yaml;
  parser reads docs/openapi.yaml since #4781 → point fixture at the new path.
  (Unit/Coverage/Node24/Node26 shard 4)
- test(integration): proxy-pipeline source-scan expected inline streaming-cost
  code that #4790/#3501 extracted to the recordStreamingCost leaf → assert the
  delegation instead. (Integration 1/2)
- fix(chatCore): derive the log trace id from crypto, not Math.random
  (CodeQL js/insecure-randomness — log-correlation id, not a secret).
- test(resilience): circuit-breaker invalid-cooldown fallback asserted t>29000,
  flaking on slow CI where ~1.6s elapsed gave t=28401 → tolerate wall-clock
  drift (t>25000). (Unit 6/8)

* fix(usage): derive pending-request id from crypto, not Math.random

CodeQL js/insecure-randomness (#669): the pending-request id generated in
trackPendingRequest (usageHistory.ts) flows into attempt logging and was flagged
as insecure randomness in a security context. It's a log-correlation id, not a
secret — switch to crypto RNG to clear the alert. Pairs with the chatCore traceId
fix in 37c49781a (same sink).

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Demiurge The Single <megamen932@gmail.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 17:06:18 -03:00

15 KiB

title, version, lastUpdated
title version lastUpdated
Release Checklist 3.8.2 2026-05-13

Release Checklist

Last updated: 2026-05-13 — v3.8.0 Streamlined release flow that leverages Claude Code skills for automation.

TL;DR

# 1. Bump version + generate CHANGELOG (skill)
/version-bump-cc patch    # or minor/major

# 2. Run quality gate locally
npm run check              # lint + tests
npm run test:coverage      # full coverage gate (75/75/75/70)

# 3. Build & smoke
npm run build
npm run test:e2e           # optional but recommended

# 4. Generate release (skill)
/generate-release-cc

# 5. Deploy (skill)
/deploy-vps-both-cc        # or akamai-cc / local-cc

# 6. Capture release evidences (skill)
/capture-release-evidences-cc

Detailed Checklist

Pre-release

  • All PRs targeted to this release are merged to release/vX.Y.0
  • All open Linear/issue items for this version are closed or pushed to next milestone
  • CI green on release/vX.Y.0 branch
  • No TODO(release) markers in code: grep -r "TODO(release)" src/ open-sse/
  • Docker base image up to date (currently node:24.15.0-trixie-slim)

Version & Changelog

  • Run /version-bump-cc <patch|minor|major> (Claude Code skill)
    • Bumps package.json, electron/package.json
    • Regenerates CHANGELOG.md from git commits since last tag
    • Updates README.md badges
  • Manually review CHANGELOG.md and clean up commit messages if needed
  • Ensure the latest semver section in CHANGELOG.md equals package.json version
  • Keep ## [Unreleased] as the first changelog section for upcoming work
  • Update docs/openapi.yamlinfo.version must equal package.json version

Code Quality

  • npm run lint — 0 errors (warnings are pre-existing)
  • npm run typecheck:core — clean
  • npm run typecheck:noimplicit:core — clean (strict)
  • npm run check:cycles — no circular deps
  • npm run check:any-budget:t11 — within budget
  • npm run check:route-validation:t06 — clean
  • npm run check:node-runtime — supported floor met (>=20.20.2 <21, >=22.22.2 <23, >=24.0.0 <25)

Testing

  • npm run test:unit — pass
  • npm run test:vitest — pass (MCP server, autoCombo, cache)
  • npm run test:coverage — gate 75/75/75/70 satisfied (statements/lines/functions/branches)
  • npm run test:integration — pass (if changes touch DB / handlers)
  • npm run test:e2e — pass (UI changes)
  • npm run test:protocols:e2e — pass (MCP/A2A changes)
  • npm run test:ecosystem — pass

Hooks (Husky validated)

Husky hooks live in .husky/ and run automatically on git operations.

  • pre-commit: npx lint-staged + node scripts/check/check-docs-sync.mjs + npm run check:any-budget:t11
  • pre-push: currently disabled (commented out). When re-enabled, runs npm run test:unit.
    • Run npm run test:unit manually before pushing release branches.

If a hook fails: fix the underlying issue, don't bypass with --no-verify.

Conventional Commits

All release-bound commits must follow type(scope): subject format.

Valid types: feat, fix, refactor, docs, test, chore, perf, style, ci

Valid scopes: db, sse, oauth, dashboard, api, cli, docker, ci, mcp, a2a, memory, skills, cloud-agent, guardrails, compression, auto-combo, resilience, providers, executors, translator, domain, authz

Breaking changes: add BREAKING CHANGE: footer or ! after the scope (e.g. feat(api)!: drop /v0).

Documentation

  • npm run check:docs-sync passes (auto-run by pre-commit)
  • npm run check:docs-all passes (umbrella: docs-sync + docs-counts + env-doc-sync + deprecated-versions + doc-links)
  • npm run check:env-doc-sync exits 0 — code ↔ .env.exampledocs/reference/ENVIRONMENT.md env contract is intact
  • npm run check:doc-links exits 0 — no broken internal markdown references after restructuring
  • docs/architecture/ARCHITECTURE.md reviewed for storage/runtime drift
  • docs/guides/TROUBLESHOOTING.md reviewed for env var and operational drift
  • If .env.example changed: docs/reference/ENVIRONMENT.md updated
  • If new feature has a UI: docs/guides/USER_GUIDE.md mentions it
  • If new feature has API: docs/reference/API_REFERENCE.md + docs/openapi.yaml updated
  • If new feature is a module: dedicated docs/<MODULE>.md exists
  • If breaking change: docs/guides/TROUBLESHOOTING.md has migration note

i18n

  • npm run i18n:check exits 0 — translation state (.i18n-state.json) in sync with source docs (no drifted sources in strict mode; warn-mode advisory is acceptable for last-minute doc touch-ups, but should be 0 before tagging)
  • npm run i18n:check-ui-coverage exits 0 — every UI locale at or above the 80% coverage floor
  • npm run i18n:sync-ui:dry reports 0 missing keys across all 42 locales
  • If source English docs changed, run npm run i18n:run (requires OMNIROUTE_TRANSLATION_API_KEY in .env) before tagging
  • Translation contributions can be deferred to next release if minor (track in CHANGELOG)

Database Migrations

  • If src/lib/db/migrations/ has new files:
    • Each migration is idempotent (CREATE TABLE IF NOT EXISTS, etc.)
    • Migrations wrapped in transactions
    • Numbered correctly (no gaps in sequence)
  • Test on fresh install: delete ~/.omniroute/omniroute.db and run npm run dev
  • Test on existing install: backup DB, run migration, verify schema
  • WAL files (-wal, -shm) handled correctly if migration rewrites tables

Provider Catalog (Zod-validated)

  • src/shared/constants/providers.ts Zod schema valid at load time
    • All providers have required fields (id, label, kind, etc.)
    • freeNote provided for new free providers
    • OAuth providers have oauthConfig registered in src/lib/oauth/constants/oauth.ts
  • If new provider added: corresponding executor in open-sse/executors/
  • If non-OpenAI format: translator in open-sse/translator/
  • Models registered in open-sse/config/providerRegistry.ts
  • Unit tests in tests/unit/ cover provider classification and routing

Desktop (Electron)

If electron/ changed:

  • npm run electron:smoke:packaged passes
  • Builds tested for at least one of :win, :mac, :linux
  • Code signing certs not expired (if signing)
  • electron/package.json version matches root package.json
  • Auto-update channel pointer updated if releasing to stable

Build Layout

The repository uses three distinct output directories — never mix them up:

Directory Purpose Tracked?
src/ Application source (TypeScript / TSX) Yes
.build/ Build intermediates — next build output (distDir) No (gitignored)
dist/ Shippable npm bundle — assembled by assembleStandalone No (gitignored)

Operator note: the remote VPS image directory remains /usr/lib/node_modules/omniroute/app/. Only the in-repo build output moved (app/dist/). The deploy skills rsync dist/ contents into the remote app/ dir — no VPS path changes required.

Single-build flow:

npm run build:release
  └─ rm -rf .build dist          (clean)
  └─ next build → .build/next/   (intermediates)
  └─ assembleStandalone          (copies standalone + static + public + natives → dist/)
  └─ writes dist/BUILD_SHA       (HEAD sentinel)

Do NOT run npm run build followed by a separate npm run build:cli for deploy — use npm run build:release which does a clean rebuild + sentinel in one command.

Artifact Validation

  • npm run build:release succeeds and dist/BUILD_SHA == git rev-parse --short HEAD
  • npm run check:pack-artifact clean — no app.__qa_backup, scripts/scratch, package-lock.json, or other local residue
  • dist/server.js exists after build

Tagging & Release

  • Run /generate-release-cc (Claude Code skill):
    • Creates tag vX.Y.Z
    • Pushes tag and branch
    • Opens GitHub Release with changelog body
    • Attaches Electron installers (if built)
  • Or manually:
    git tag -a vX.Y.Z -m "Release vX.Y.Z"
    git push origin vX.Y.Z
    gh release create vX.Y.Z --notes-from-tag
    

Deploy

Deploy skills use the light rsync flow — no npm pack, no npm i -g:

  • Use deploy skill that matches target:
    • /deploy-vps-local-cc — local VPS (192.168.0.15)
    • /deploy-vps-akamai-cc — Akamai VPS (69.164.221.35)
    • /deploy-vps-both-cc — both
  • Before deploying, confirm dist/BUILD_SHA == git rev-parse --short HEAD
  • Build must run where node_modules is real (main checkout or npm ci'd worktree — NOT a symlinked worktree)
  • Smoke test deployed instance:
    • Open /dashboard/health → check version string matches release
    • Run a /v1/chat/completions request against a known provider
    • Verify /api/monitoring/health returns CLOSED circuit breakers
    • Confirm MCP transports respond (/mcp HTTP, /mcp-sse SSE)

Post-release

  • Run /capture-release-evidences-cc (Claude Code skill)
    • Captures WebP screenshots/recordings of new features
    • Attaches to release notes / blog post
  • Update GitHub Discussions / Discord with release announcement
  • Open milestone for next version
  • If critical: pin discussion or post in news.json for in-app banner

Embedded Services smoke (v3.8.4+)

Before shipping any release that includes embedded services changes, verify:

Fresh-DB boot (catches migration collisions — added after v3.8.4 hotfix)

  • DATA_DIR=$(mktemp -d) npm start & — wait 10 s for boot
  • curl -s http://127.0.0.1:20128/api/services/9router/status | jq '.tool' returns "9router" (NOT 404, NOT 500). Confirms migration 071_services.sql applied + row seeded.
  • sqlite3 $DATA_DIR/storage.sqlite "PRAGMA table_info(version_manager);" | grep -E "provider_expose|logs_buffer_path|last_sync_at" returns 3 rows.
  • sqlite3 $DATA_DIR/storage.sqlite "PRAGMA table_info(webhooks);" | grep -E "kind|metadata_encrypted" returns 2 rows (validates 070_webhooks_kind_metadata.sql applied).
  • node --import tsx/esm --test tests/unit/db/no-migration-collisions.test.ts passes — guards against future collisions.

9Router

  • POST /api/services/9router/install returns 200 with installedVersion in under 2 min
  • POST /api/services/9router/start returns 200 and state: "running" in under 30 s
  • GET /api/services/9router/status reports health: "healthy"
  • POST /v1/chat/completions with "model": "9router/auto/..." returns 200 (end-to-end routing through 9Router)
  • GET /dashboard/providers/services/9router/embed/dashboard renders the 9Router native UI inside the proxy (no direct 127.0.0.1:port iframe)
  • POST /api/services/9router/rotate-key returns { keyRotated: true } and service restarts cleanly
  • POST /api/services/9router/stop returns 200 and state: "stopped"
  • GET /api/services/9router/logs?tail=50 returns SSE stream with snapshot event containing recent lines
  • Install in environment without npm in PATH returns 500 with a friendly (non-stack-trace) error message

CLIProxyAPI

  • POST /api/services/cliproxy/install returns 200 in under 2 min
  • POST /api/services/cliproxy/start returns 200 and state: "running" in under 30 s
  • GET /api/services/cliproxy/status reports health: "healthy"
  • POST /api/services/cliproxy/stop returns 200 and state: "stopped"
  • GET /api/services/cliproxy/logs?tail=50 returns SSE stream

Security regression

  • curl -H "X-Forwarded-For: 1.2.3.4" http://localhost:20128/api/services/9router/start returns 403 LOCAL_ONLY
  • curl -H "X-Forwarded-For: 1.2.3.4" http://localhost:20128/api/services/cliproxy/start returns 403 LOCAL_ONLY
  • Error responses from /api/services/* do not contain err.stack or absolute file paths

v3.8.0+ checks

Before shipping any v3.8.x release, verify these additional items:

  • omniroute --tray boots on macOS (systray2 installed into ~/.omniroute/runtime/)
  • omniroute --tray boots on Linux (requires DISPLAY; graceful error if not set)
  • omniroute --tray boots on Windows (PowerShell NotifyIcon, no extra binaries)
  • omniroute config tray enable creates autostart entry; disable removes it
  • npm install -g omniroute@<this-version> runs postinstall without fatal exit
  • Update path keeps optional deps: omniroute update --apply and the auto-updater run npm install -g … --include=optional so optionalDependencies (better-sqlite3, keytar, tls-client, and the llmlingua SLM stack: @atjsh/llmlingua-2, @tensorflow/tfjs, js-tiktoken) survive an update. The ultra modelPath SLM tier additionally needs @huggingface/transformers@3.5.2 (pinned — llmlingua-2 uses the 3.x tokenizer API) and the tinybert model, auto-downloaded to ${DATA_DIR}/models/llmlingua on first use. Postinstall (scripts/build/colocateOptionals.mjs) then co-locates the SLM optional closure into dist/node_modules so the worker resolves a SINGLE @huggingface/transformers 3.5.2 instance — the standalone trace bundles only transformers, not the dynamically-imported optionals, so without this the worker would load llmlingua-2 against the root's transformers and the SLM tier would silently fail-open.
  • omniroute status works with no .env (CLI token path, loopback only)
  • curl http://localhost:20128/api/shutdown returns 401 (always-protected route)
  • curl -H "host: evil.com" http://localhost:20128/api/mcp/sse returns 401 (loopback guard)
  • SQLite runtime resolves to bundled on first run (bundled binary valid for platform)
  • SQLite runtime falls back to runtime when node_modules/better-sqlite3 is deleted
  • Smart MCP filter compresses real playwright-mcp browser_snapshot output (≥50% reduction)
  • All 10 skills/omniroute*/SKILL.md files are publicly fetchable via raw GitHub URL
  • Onboarding wizard shows "How It Works" tier tour step on fresh setup
  • Home dashboard tier coverage widget shows configured/active counts

Rollback

If release has critical issue:

  1. gh release edit vX.Y.Z --prerelease (marks as not latest)
  2. git tag -d vX.Y.Z && git push --delete origin vX.Y.Z (only if not yet adopted by users)
  3. Or: hotfix on release/vX.Y.0 → patch release vX.Y.(Z+1)
  4. Communicate in GitHub Discussions and Discord immediately

Hard Rules

  • Never commit directly to main
  • Never use git push --force to main or release/* branches
  • Never skip Husky hooks (--no-verify)
  • Never commit secrets, credentials, or .env files
  • Coverage must stay ≥75/75/75/70 (statements/lines/functions/branches)
  • Always include or update tests when changing production code in src/, open-sse/, electron/, or bin/

Automated Sync Check

Run the docs sync guard locally before opening a PR:

npm run check:docs-sync

CI also runs this check in .github/workflows/ci.yml (lint job).