docs/guides/TROUBLESHOOTING.md
For Users: Looking for quick fixes? See the Quick Reference below.
๐ Languages: ๐บ๐ธ English | ๐ง๐ท Portuguรชs (Brasil) | ๐ช๐ธ Espaรฑol | ๐ซ๐ท Franรงais | ๐ฎ๐น Italiano | ๐ท๐บ ะ ัััะบะธะน | ๐จ๐ณ ไธญๆ (็ฎไฝ) | ๐ฉ๐ช Deutsch | ๐ฎ๐ณ เคนเคฟเคจเฅเคฆเฅ | ๐น๐ญ เนเธเธข | ๐บ๐ฆ ะฃะบัะฐัะฝััะบะฐ | ๐ธ๐ฆ ุงูุนุฑุจูุฉ | ๐ฏ๐ต ๆฅๆฌ่ช | ๐ป๐ณ Tiแบฟng Viแปt | ๐ง๐ฌ ะัะปะณะฐััะบะธ | ๐ฉ๐ฐ Dansk | ๐ซ๐ฎ Suomi | ๐ฎ๐ฑ ืขืืจืืช | ๐ญ๐บ Magyar | ๐ฎ๐ฉ Bahasa Indonesia | ๐ฐ๐ท ํ๊ตญ์ด | ๐ฒ๐พ Bahasa Melayu | ๐ณ๐ฑ Nederlands | ๐ณ๐ด Norsk | ๐ต๐น Portuguรชs (Portugal) | ๐ท๐ด Romรขnฤ | ๐ต๐ฑ Polski | ๐ธ๐ฐ Slovenฤina | ๐ธ๐ช Svenska | ๐ต๐ญ Filipino | ๐จ๐ฟ ฤeลกtina
Common problems and solutions for OmniRoute.
New to OmniRoute? Start here โ these solve 90% of problems:
| I see this | What it means | What to do |
|---|---|---|
| "Can't connect" | OmniRoute isn't running | Run omniroute or docker restart omniroute |
| "Invalid API key" | Your key is wrong or expired | Re-copy the key from the provider's website |
| "Rate limit exceeded" | You're sending too many requests | Wait 1 minute, or use model: "auto" for automatic fallback |
| "Quota exceeded" | You've used up your free/paid quota | Connect more providers, or use free providers (Kiro, Pollinations) |
| "Slow responses" | Provider is busy or far away | Use model: "auto/fast" or connect a faster provider (Groq, Cerebras) |
| "Wrong provider used" | auto picked a different provider | That's normal! auto picks the best one. Force a specific provider with model: "openai/gpt-4o" |
| "502 Bad Gateway" | Provider is down | Wait and retry, or use model: "auto" to switch providers |
| "401 Unauthorized" | Your credentials are wrong | Check your API key or re-authenticate with OAuth |
| "429 Too Many Requests" | Rate limited | Wait 1 minute, or connect more providers |
Still stuck? See the detailed troubleshooting below, or ask on Discord.
When you run npm install -g omniroute, you may see a wall of warnings like npm warn ERESOLVE, peer-dependency notices, and deprecated messages. These are expected and harmless. Your install succeeded if you see added <N> packages in the output.
The warnings come from stale peer-dependency ranges in third-party packages OmniRoute doesn't control:
marked-terminal wants marked >=1 <16, found marked@18 โ works fine in practice; the upstream peer range is just stale.deprecated [email protected] โ the native-binary fetch helper. Only relevant later if a web-cookie provider reports a missing tls-client-node native binary (a separate issue, not caused by this warning).No action needed โ the warnings cannot be fully silenced without forking upstream packages.
| Problem | Solution |
|---|---|
| First login not working | Set INITIAL_PASSWORD in .env (no hardcoded default) |
| Dashboard opens on wrong port | Set PORT=20128 and NEXT_PUBLIC_BASE_URL=http://localhost:20128 |
| No logs written to disk | Set APP_LOG_TO_FILE=true and verify call log capture is enabled |
| EACCES: permission denied | Set DATA_DIR=/path/to/writable/dir to override ~/.omniroute |
| Routing strategy not saving | Update to the latest v3.x release (Zod schema fix for settings persistence shipped in earlier versions) |
| Login crash / blank page | Check Node.js version โ see Node.js Compatibility below |
dlopen / slice is not valid mach-o file (macOS) | Run cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute โ see macOS native module rebuild below |
| Proxy "fetch failed" | Ensure proxy config is set at the correct level โ see Proxy Issues below |
Docker curl: (56) Recv failure: Connection reset by peer | Your Docker port bind may be landing on IPv6. Use -p 127.0.0.1:20128:20128 to force IPv4, or test with curl -4. See Docker IPv6 below |
Antivirus quarantines README.md | False positive โ see Antivirus false positives below |
| Kaspersky flags the Desktop app as a Trojan | Behavioral false positive on the unsigned installer โ see Antivirus false positives below |
<a name="antivirus-false-positives"></a>
README.md with MD:HttpRequest-inf[Susp]This is a false positive. Nothing is infected, and no action is required.
Avast and AVG run a heuristic that flags plain-text/Markdown files containing many
HTTP-request-looking links. OmniRoute's README.md ships inside the npm package (it is
listed in package.json โ files), so it lands at node_modules/omniroute/README.md on
a global install โ and it contains ~15 http://localhost:20128/... examples (the MCP
HTTP/SSE endpoints, the A2A .well-known URL, and curl snippets). That link density is
enough to trip the heuristic.
If this started only recently: the file did not change in kind. The README grew its
endpoints table (MCP HTTP + SSE + A2A were added) and more curl examples, which pushed
it past the threshold.
The file is inert documentation with zero executable content. You can safely restore it from quarantine.
What to do:
node_modules path and/or the
OmniRoute data dir (~/.omniroute/).README.md. This is the fix that helps everyone, since it is
the vendor's heuristic overreacting to a text file.Why we do not "fix" this on our side: the examples are all http://localhost, and
localhost cannot be https without self-signed-certificate friction. Mangling the docs to
dodge one vendor's heuristic would hurt every reader to satisfy a scanner bug.
PDM:Trojan.Win32.GenericThis is a false positive from a behavioral heuristic. Nothing is infected. Kaspersky's
PDM: prefix means the verdict comes from its Proactive Defense Module (System Watcher),
which judges what the installer does rather than matching it against known malware. When
it fires, Kaspersky "rolls back" the whole installation โ deleting files it had already
written โ so the app ends up broken or missing.
The files it flags are stock parts of declared, open-source dependencies bundled with the desktop app, for example:
resources/app/.build/next/node_modules/playwright-<hash>/lib/โฆ/agentParser.js and
workerProcessEntry.js โ Playwright, the browser-automation
library used for in-app provider login and browser-backed chat.resources/app/.build/next/node_modules/tls-client-node-<hash>/bin/tls-client-windows-64-<ver>.dll
โ the native binary from tls-client-node, used for Cloudflare-tolerant HTTP on some web
providers.Why it fires: the Windows installer is not yet code-signed, so an unsigned NSIS
installer has zero reputation and behavioral heuristics run at maximum aggression. Combined
with a bundled native DLL and hundreds of .js files written under
%LOCALAPPDATA%\Programs\OmniRoute (including hash-suffixed package directories from the
Next.js standalone build), that is enough to trip the heuristic. Code signing is planned;
until it lands, new releases can repeat this.
What to do:
latest.yml, whose sha512 field (base64) covers the OmniRoute.Setup.<version>.exe
installer. In PowerShell, from the folder containing the installer:
$b = [System.Security.Cryptography.SHA512]::Create().ComputeHash(
[System.IO.File]::ReadAllBytes("$PWD\OmniRoute.Setup.<version>.exe"))
[Convert]::ToBase64String($b)
latest.yml โ sha512. If it does not, delete the file and
re-download only from the GitHub releases page.%LOCALAPPDATA%\Programs\OmniRoute (Kaspersky โ Settings โ Threats and Exclusions),
then reinstall.<a name="nodejs-compatibility"></a>
Cause: You are running a Node.js version outside OmniRoute's approved secure runtime floor. The most common case is running an older Node 22 or 24 patch level that falls below the patched security floor OmniRoute requires.
Symptoms:
Error: Module did not self-register or similar native binding errorsFix:
nvm install 24
nvm use 24
node --version should show v24.0.0 or newer on the 24.x LTS linenpm install -g omnirouteomnirouteSupported secure versions:
>=22.22.2 <23or>=24.0.0 <27. Node.js 24.x LTS (Krypton) and Node.js 26 are fully supported.
better-sqlite3 not installed (Cannot find module)<a name="npm-v11-better-sqlite3-not-installed-cannot-find-module"></a>
Cause: npm v11 (shipped with Node.js 24+) blocks install scripts for optional
dependencies by default. Since better-sqlite3 is listed in optionalDependencies
and requires native compilation (node-gyp rebuild), npm silently skips it.
Symptoms:
Cannot find module 'better-sqlite3'ls node_modules/better-sqlite3 shows "No such file or directory"npm ls better-sqlite3 shows (empty)Fix:
npm approve-scripts better-sqlite3
npm install
npm pack [email protected]
tar -xzf better-sqlite3-*.tgz -C node_modules
mv node_modules/package node_modules/better-sqlite3
rm better-sqlite3-*.tgz
node -e "require('better-sqlite3')(':memory:').close(); console.log('OK')"dlopen / "slice is not valid mach-o file"<a name="macos-native-module-rebuild"></a>
Cause: After a global npm install -g omniroute, the better-sqlite3 native binary inside the package may have been compiled for a different architecture or Node.js ABI than what is running locally. This is common on macOS (both Apple Silicon and Intel) when the pre-built binary does not match your environment.
Symptoms:
dlopen errorslice is not valid mach-o filedlopen(/Users/<user>/.nvm/versions/node/v24.14.1/lib/node_modules/omniroute/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node, 0x0001): tried: '...' (slice is not valid mach-o file)
Fix โ rebuild for your local environment (no Node.js downgrade required):
cd $(npm root -g)/omniroute/app
npm rebuild better-sqlite3
omniroute
Note: This recompiles the native binding against your local Node.js version and CPU architecture, resolving the binary mismatch. The officially supported runtime range is
>=22.22.2 <23or>=24.0.0 <27(SUPPORTED_NODE_RANGEinsrc/shared/utils/nodeRuntimeSupport.ts, aligned with thepackage.jsonenginesfield). Node.js 24.x LTS (Krypton) and Node.js 26 are fully supported withbetter-sqlite3v12.x.
<a name="proxy-issues"></a>
Cause: The API key validation endpoint (POST /api/providers/validate) was previously bypassing proxy configuration, causing failures in environments that require proxy routing.
Fix (v3.5.5+): This is now fixed. Provider validation routes through runWithProxyContext, honoring provider-level and global proxy settings automatically.
Cause: Background OAuth token refresh was not resolving proxy configuration per connection.
Fix (v3.5.5+): The token health check scheduler now resolves proxy config per connection before attempting refresh. Update to v3.5.5+.
Cause: On Node.js 22, the undici@8 dispatcher is incompatible with Node's built-in fetch() implementation.
Fix (v3.5.5+): OmniRoute now uses undici's own fetch() function when a proxy dispatcher is active, ensuring consistent behavior. Update to v3.5.5+.
Cause: The MITM proxy and its CA certificate install into the environment where OmniRoute runs. Under WSL that environment is the Linux guest, while the AI desktop apps (Kiro, Trae, Copilot, Zed, โฆ) run on the Windows host. The host apps do not trust the guest's certificate store and do not route through the guest's system proxy, so desktop interception does not engage there.
Recommendation: Run OmniRoute natively on the same OS as the desktop apps you want to intercept (Windows for Windows apps; macOS/Linux likewise). Keeping OmniRoute inside WSL while targeting host apps requires manually trusting the generated CA certificate on the Windows host and pointing each host app's network/proxy settings at the WSL proxy endpoint โ an unsupported, fragile setup.
Cause: Provider quota exhausted.
Fix:
Cause: Subscription quota exhausted.
Fix:
cc/claude-opus-4-6 โ glm/glm-4.7 โ if/qwen3.8-max-previewOmniRoute auto-refreshes tokens. If issues persist:
Cause: Kiro's backend enforces a single active session per OIDC client registration. When two accounts share the same registered client (connections imported before v3.8.0), refreshing one account's token invalidates the other's refresh token.
Fix (v3.8.0+): Re-import affected connections. Starting with v3.8.0, every new Kiro connection created via Import Token, Google/GitHub social login, or Auto-Import automatically registers its own dedicated OIDC client. The connection is therefore fully isolated and refreshing one account has no effect on any other account.
Connections that were imported before v3.8.0 do not carry a per-connection client registration. Those connections continue to use the shared social-auth refresh endpoint. To gain isolation, delete the old connection from Dashboard โ Providers and re-add it via any of the three import flows.
For full details and step-by-step instructions for adding two Kiro accounts side by side,
see docs/guides/KIRO_SETUP.md.
BASE_URL points to your running instance (e.g., http://localhost:20128)CLOUD_URL points to your cloud endpoint (e.g., https://omniroute.dev)NEXT_PUBLIC_* values aligned with server-side valuesstream=false Returns 500Symptom: Unexpected token 'd'... on cloud endpoint for non-streaming calls.
Cause: Upstream returns SSE payload while client expects JSON.
Workaround: Use stream=true for cloud direct calls. Local runtime includes SSEโJSON fallback.
/api/keys)401 on cloud<a name="docker-ipv6"></a>
Symptoms: curl http://localhost:20128/v1/models returns curl: (56) Recv failure: Connection reset by peer. Dashboard and unauthenticated endpoints work, but authenticated endpoints fail โ it looks like an auth problem but isn't.
Cause: docker run -p 20128:20128 publishes on both 0.0.0.0 (IPv4) and :: (IPv6), but the process inside the container listens on IPv4 only. On hosts where localhost resolves to ::1 first, the connection lands on the IPv6 published port with no listener behind it โ connection reset.
Fix:
curl -4 http://localhost:20128/v1/models. If it works with -4 but fails without, you have an IPv6 bind mismatch.-p 127.0.0.1:20128:20128 in your docker run command:
docker run -d --name omniroute --restart unless-stopped --stop-timeout 40 \
-p 127.0.0.1:20128:20128 -v omniroute-data:/app/data diegosouzapw/omniroute:latest
curl http://localhost:20128/api/cli-tools/runtime/codex | jqrunner-cli (bundled CLIs)CLI_EXTRA_PATHS and mount host bin directory as read-onlyinstalled=true and runnable=false: binary was found but failed healthcheckcurl -s http://localhost:20128/api/cli-tools/codex-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}'
curl -s http://localhost:20128/api/cli-tools/claude-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}'
curl -s http://localhost:20128/api/cli-tools/openclaw-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}'
Set APP_LOG_TO_FILE=true in your .env file. Application logs are written under logs/.
Request artifacts are stored under ${DATA_DIR}/call_logs/ when the call log pipeline is
enabled in settings.
When pipeline capture is enabled, set CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS=false to omit
stream chunk payloads, or tune CALL_LOG_PIPELINE_MAX_SIZE_KB to change the artifact cap in KB.
# Health dashboard
http://localhost:20128/dashboard/health
# API health check
curl http://localhost:20128/api/monitoring/health
${DATA_DIR}/storage.sqlite (providers, combos, aliases, keys, settings)storage.sqlite (usage_history, call_logs, proxy_logs) + optional ${DATA_DIR}/call_logs/<repo>/logs/... (when APP_LOG_TO_FILE=true)${DATA_DIR}/call_logs/YYYY-MM-DD/... when the call log pipeline is enabledThe Request Logs page's Clean history action clears call_logs, legacy
request_detail_logs, and the local ${DATA_DIR}/call_logs/ artifact directory.
When a provider's circuit breaker is OPEN, requests are blocked until the cooldown expires.
Fix:
If a provider repeatedly enters OPEN state:
deepgram/nova-3 or assemblyai/bestmp3, wav, m4a, flac, ogg, webmUse Dashboard โ Translator to debug format translation issues:
| Mode | When to Use |
|---|---|
| Playground | Compare input/output formats side by side โ paste a failing request to see how it translates |
| Chat Tester | Send live messages and inspect the full request/response payload including headers |
| Test Bench | Run batch tests across format combinations to find which translations are broken |
| Live Monitor | Watch real-time request flow to catch intermittent translation issues |
x_groq, usage_breakdown, etc.) that cause OpenAI SDK Pydantic validation failures. If you still see this on v3.x+, please file an issue.system role โ Resolved in v1.x; role normalizer automatically merges system messages into user messages for incompatible models. If you still see this on v3.x+, please file an issue.developer role not recognized โ Resolved in v1.x; automatically converted to system for non-OpenAI providers. If you still see this on v3.x+, please file an issue.json_schema not working with Gemini โ Resolved in v1.x; response_format is now converted to Gemini's responseMimeType + responseSchema. If you still see this on v3.x+, please file an issue.429 status codes or Retry-After headersProvider profiles support these settings:
When many concurrent requests hit a rate-limited provider, OmniRoute uses mutex + auto rate-limiting to serialize requests and prevent cascading failures. This is automatic for API key providers.
Symptoms:
503 response whose error code is
chat_admission_busy.Retry-After; the byte-based path uses 2 seconds, while the
structure-based path uses 1 second and includes reason: "structure_limit".The byte-based response body is:
{
"error": {
"message": "Chat admission capacity is temporarily unavailable. Retry shortly.",
"type": "server_error",
"code": "chat_admission_busy"
}
}
The structure-based response uses the same type and code, with the message
Structurally heavy chat request capacity is busy; retry shortly. and
reason: "structure_limit".
At the default thresholds, a request is structurally heavy when it has at least 200 messages,
at least 64 tools, or at least 32,000 estimated tokens, or when bounded structure estimation
exhausts its bounds of 10,000 visited nodes or depth 12.
Cause: This is deliberate load shedding inside OmniRoute, not an upstream-provider failure. Each process uses a process-local guard to reserve limited heavyweight capacity before retaining and parsing a large request body. A heavyweight lease remains held for the lifetime of an SSE response.
When capacity is busy, a heavyweight request first waits up to
OMNIROUTE_CHAT_ADMISSION_QUEUE_MS (default 5000, 0 disables the wait) for a slot to free up
before answering the retryable 503. The bounded wait exists so agent-style clients
(OpenCode, Claude Code, Cursor) that fan out heavy sub-requests concurrently serialize the burst
instead of burning their whole retry budget on immediate rejections and dying mid-task.
Current heavyweight lease occupancy is not surfaced in the dashboard.
Settings โ Resilience โ Request Queue โ Concurrent Requests does not control this; that setting
governs a separate provider request-queue mechanism.
Fix:
Retry-After and use backoff rather than immediately
repeating the request. Note that with the default OMNIROUTE_CHAT_ADMISSION_QUEUE_MS=5000
a heavy request already waited up to 5 seconds before the 503, so a client retry loop should
back off beyond that instead of hammering.OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT from its default of 1. Increase it one step at a time,
restart OmniRoute after each change, and observe memory headroom under representative load.
Every additional heavyweight request can increase concurrent V8 heap use and container or
host OOM risk. No value is safe for every deployment; validate the setting against your own
traffic and memory limits rather than assuming that 2 is universally safe.OMNIROUTE_CHAT_ADMISSION_QUEUE_MS) over raising the in-flight
limit when bursts are short: waiting costs latency, while an extra concurrent heavyweight
request costs heap residency for the whole request lifetime.See the environment-variable reference for the authoritative admission settings. Loosening the heavyweight classification thresholds can let expensive requests bypass this guard and is riskier than a cautious in-flight increase.
Some OmniRoute users place the gateway in front of RAG or agent stacks. In those setups it is common to see a strange pattern: OmniRoute looks healthy (providers up, routing profiles ok, no rate limit alerts) but the final answer is still wrong.
In practice these incidents usually come from the downstream RAG pipeline, not from the gateway itself.
If you want a shared vocabulary to describe those failures you can use the WFGY ProblemMap, an external MIT license text resource that defines sixteen recurring RAG / LLM failure patterns. At a high level it covers:
The idea is simple:
No.1 โฆ No.16).Full text and concrete recipes live here (MIT license, text only):
You can ignore this section if you do not run RAG or agent pipelines behind OmniRoute.
Issues specific to the v3.8.0 release and their current workarounds. If a fix lands in a later patch, the entry will be updated or removed.
Symptoms:
installed=falseCauses:
CLI_DEVIN_BIN points to a path that does not existFix:
CLI_DEVIN_BIN=/usr/local/bin/devin (or the real path) in .envSymptoms:
Manual reset:
DELETE /api/resilience/model-cooldowns with management auth headersSymptoms:
Cause: The OAuth flow did not complete (callback not received or token not persisted).
Fix:
omniroute providers from the CLI to re-trigger the OAuth flow, orSymptoms:
Cause: ModelScope emits provider-specific Retry-After headers. v3.8.0 ships dedicated handling for those headers, so older versions misread them as generic rate-limit hints.
Fix:
useUpstream429BreakerHints toggle is enabled under Settings โ ResilienceSymptoms:
Cause: The OMNIROUTE_WS_BRIDGE_SECRET env var is missing from the production environment.
Fix:
openssl rand -hex 32OMNIROUTE_WS_BRIDGE_SECRET=<random-secret> in the production server env (and any client that talks to the bridge)Symptoms:
background mode degraded to synchronousbackground: true request returns a normal synchronous response instead of a background job handleCause: v3.8.0 intentionally degrades background: true on the Responses API to synchronous execution while emitting a warning. Full async background execution is a future deliverable.
Fix:
background, ordocs/architecture/ARCHITECTURE.md for internal detailsdocs/reference/API_REFERENCE.md for all endpoints