DurinDoor

Troubleshooting

Diagnose startup, authentication, provider, streaming, database, and local intercept failures.

Check the process first, then the client URL and gateway key, then the provider connection.

curl http://localhost:20128/api/health
curl http://localhost:20128/v1/models
curl http://localhost:20128/v1/realtime/auth \
  -H "Authorization: Bearer YOUR_DURINDOOR_API_KEY"

Health returns { "ok": true }. On loopback, the models catalog does not validate keys. With Require API Key enabled, the auth probe validates the key. With enforcement off, trusted loopback admits missing and unknown unsaved keys; paused or expired stored keys are still rejected. Remote HTTP /v1 requests require a valid gateway key or CLI credential regardless of that setting. None of these checks proves provider inference works.

Health check fails

Confirm the process and published port. Production and CLI use 20128; source npm run dev uses 20127. Docker needs -p 20128:20128. Check firewall rules and use the reachable host instead of localhost for a remote install.

Retry /api/health on that port. A 200 confirms process reachability. See Startup.

Port already in use

Stop the conflicting listener or choose another port with --port or PORT. The CLI attempts port cleanup during normal startup; it preserves its own process tree and stops if MITM cleanup cannot be confirmed. Readiness waits up to 60 seconds.

Verify /api/health answers on the selected port and that clients use the same port.

JWT_SECRET missing

Fresh installations need JWT_SECRET; an older DATA_DIR/jwt-secret file remains a compatibility fallback. Missing both fails when a dashboard session is signed or verified.

Set a stable value, such as one generated by openssl rand -hex 32, and restart. Verify dashboard login completes. See Security.

Cutover stops midway

Read databaseEngineError on Settings → Database. Fix the Postgres URL, cluster, disk space, or permissions, then retry cutover. cutover snapshot failed aborts the switch; Mirror row-count mismatch means the copied data did not match. HTTP 503 with A cutover is already in flight means another cutover or rollback holds the lock. Writes can fail with database cutover in progress while reads continue.

Verify engine status after retry. A data.sqlite.postgres-cutover-* snapshot alone does not prove the engine switched. A crash after saving the Postgres setting can make the next boot try Postgres. See Postgres.

Engine says postgres but this process is on SQLite

If Postgres failed to open, engine status reports servingFallback: true and the dashboard shows Serving SQLite. New writes stay local.

Restore the URL or cluster, then restart. Verify servingFallback is false and the dashboard shows the active Postgres engine. See Postgres.

SQLite driver fallback

Node tries better-sqlite3, then node:sqlite on Node 22.5 or later, then sql.js. A native-driver warning is not a boot failure when a later driver opens. No SQLite driver available means every option failed.

For a failed native binding, reinstall the CLI package. sql.js can run a small installation. Verify the startup log names a successful driver and /api/health answers.

Invalid API key

HTTP 401 can mean a missing, expired, paused, truncated, or wrong key. Use a DurinDoor dashboard key, not the provider's key. Changing API_KEY_SECRET can invalidate checksum-protected keys. Realtime reports 4001 and invalid_api_key.

Paste the full active gateway secret. Verify /v1/realtime/auth with that key, then retry the original request. See API keys.

Model not found

Copy a callable ID from /v1/models or the dashboard. Unknown providers return 404 model_not_found; retired IDs can return 410 model_shutdown. A configured combo provides a stable client name.

Retry with the selected ID. A catalog listing alone does not prove provider access. See First request.

Provider not configured

404 provider_not_configured means the provider is routable but has no active credential. Add or reconnect an account under Providers and enable it.

Verify the connection's test result, then retry inference. See Connecting accounts.

Provider authentication or network failure

Read the stored connection error. For upstream 401 or 403, reconnect OAuth or replace the provider key. For fetch failed, DNS, or ECONNREFUSED, check connectivity from the DurinDoor host to the configured upstream.

Verify the provider connection test, then repeat the original request.

Rate limit or quota skip

Wait for the reset shown on Usage or Quota Tracker, honor Retry-After, or add an account or compatible combo fallback. Account cooldown, quota reservations, and concurrency admission can each block a request.

503 Provider quota capacity unavailable means no quota reservation was available. 503 Concurrency limit reached names the provider or __global__ tier. globalConcurrentRequests: 0 means no global cap; providerConcurrencyLimits applies per provider.

A known upstream retry hint is passed through; otherwise a rate-limit response can advertise exponential backoff. Billing exhaustion still cools the account but omits Retry-After. Verify the reset or capacity before retrying. See Quota tracking.

Request aborted

HTTP 499 Request aborted means the client disconnected or cancelled before dispatch. Retry once. For repeated failures, check client cancellation and proxy idle timeouts.

Verify the client keeps the connection open until the response ends.

Input exceeds context window

HTTP 400 with input is too long means input plus the effective output reservation exceeds a known window, or input exceeds a separate input ceiling. This error ends combo fallback.

Shorten input, lower the output limit, or select a model with a larger window. Verify a smaller request succeeds. See Model limits.

Translation or outbound validation failed

For 400 Failed to translate request or Outbound validation failed, match the body to the endpoint: OpenAI chat on /v1/chat/completions, Claude messages on /v1/messages. Remove the failing optional tool schema or select a compatible provider.

Verify a minimal text request first, then restore tools individually.

Failed to process provider response

HTTP 502 Failed to process provider response means the gateway could not decode the upstream body. Retry once, then select another compatible model if the failure repeats.

Inspect Usage → Details for the sanitized upstream status and keep the response's x-request-id for diagnosis.

Streaming cuts off

Test the same request directly on localhost. If it succeeds there, raise the reverse proxy's read and send timeouts. If the model cannot stream, use "stream": false when the client supports it.

Verify the stream reaches its terminal event. See Reverse proxy and static assets.

Streaming request fails after keepalive frames

Responses and Claude message streams can open HTTP 200 after about two seconds of upstream silence. A later failure arrives as the final SSE event because the HTTP status is already sent.

EndpointError event fields
/v1/responsesevent: error; JSON type: "error", code, message, param, sequence_number, status_code, error_type, and optional retry_after_seconds.
/v1/messagesevent: error; JSON type: "error" and nested error with Anthropic type, message, status_code, and optional retry_after_seconds.

Read the event's status instead of relying on HTTP 200. Retry 429 and 5xx failures according to the cooldown. retry_after_seconds derives from Retry-After, is capped at 3600, and is omitted without a hint. Verify the client handles that final event.

Dashboard login loops or is refused

Keep JWT_SECRET stable and allow session cookies. With HTTPS, set AUTH_COOKIE_SECURE=true and the proxy's X-Forwarded-Proto. Secure cookies cannot work over plain HTTP. Origin checks also apply.

Remote login with built-in password 123456 returns 403 mustChangePassword. Change it on loopback first. Five failed attempts from an IP trigger 429; wait for Retry-After. OIDC mode disables password login. Verify a new session survives a dashboard reload. See Security.

MFA verification fails

For MFA session expired, sign in with the password again before submitting the second factor. For an invalid code, check the authenticator's clock or use an unused backup code. Repeated failures share the login lockout and return 429 with Retry-After.

Verify login completes and retain the remaining backup codes securely. MFA setup and disable require same-origin requests and the current password; disable also needs a valid second factor.

Docker cannot reach a local model

Container localhost refers to the container. Set the provider baseUrl to host.docker.internal where supported, or a reachable host or Compose service name. The gateway must bind 0.0.0.0 inside the container and publish its port.

Verify connectivity from the container, then run the provider test. See Docker and Free and local.

MITM Root CA, lock, or redirect errors

Run DurinDoor as a standard user. The proxy stays unprivileged; sudo or UAC handles only certificate, hosts-file, firewall, and port-redirect changes. Another start can report MITM server is already starting or lock contention.

macOS and Linux listen on 127.0.0.1:8443 with a user-scoped redirect from port 443. Windows binds loopback 443 directly. The redirect journal belongs to the OS user, independently of DATA_DIR:

  • macOS/Linux: ~/.durindoor-mitm-state/redirect.json.
  • Windows: %USERPROFILE%\AppData\Local\DurinDoor\mitm-state\redirect.json.

A live integer-only .mitm.pid belongs to the older privileged launcher. Stop MITM with the old version before upgrading. If that is impossible, close DurinDoor and reboot. Never raw-kill the recorded PID; another process can reuse it.

MITM_PRIVILEGED_OPERATION_UNCERTAIN, or a journal marked installing or uncertain, requires recovery. Close every DurinDoor process and reboot to end the unconfirmed privileged process tree. Inspect and remove only the current user's exact DurinDoor redirect rule, verify its absence, then delete that user's redirect.json. Preserve .mitm.pid, Root CA files, and trust-rotation journals.

Verify the user's redirect rule is absent before restarting MITM. For certificate failures, confirm the Root CA can be generated, read, and trusted through the dashboard workflow.

Sign in to the provider website again, copy a fresh live request's Cookie header, and replace the connection value with Edit. DurinDoor does not read cookie expiry; upstream 401 or 403 causes a two-minute cooldown.

Verify the connection test after replacement. See Web cookie providers.

Provider returns 501 provider_port_pending

The catalog entry has no ported executor. Pick another provider or compatible combo member, then verify a request succeeds.

See Hidden and retired providers.

OAuth login never finishes

From a remote browser, a loopback callback cannot reach the server. Copy the failed page's full URL into the modal's paste step. Claude Code, Antigravity, and Gemini CLI use localhost:<dashboard port>/callback; Codex uses localhost:1455/auth/callback.

If Codex reports port 1455 in use, stop the conflicting login listener and retry. Verify the dashboard connection becomes active. See OpenAI Codex, Claude Code, and Antigravity.

Google rejects the Antigravity or Gemini CLI login

Set both the provider's OAuth client ID and secret, then restart. ANTIGRAVITY_OAUTH_CLIENT_ID and ANTIGRAVITY_OAUTH_CLIENT_SECRET, or the GEMINI_OAUTH_* pair, default to empty values.

Restart the authorize flow and verify consent completes. See Antigravity setup.

Connection shows reauth_required

Sign in again on that Codex or OrcaRouter connection. An invalidated or reused token disables the account; another account's success does not clear it.

Verify the affected connection is active after login. See OpenAI Codex.

Kiro says profileArn is required

Reconnect with Builder ID or Identity Center to fetch the ARN. Missing ARNs can occur outside us-east-1, without Amazon Q Developer Pro, or after a macOS token import.

Verify the reconnected account test. See Kiro errors.

Problems after an upgrade

Restore the pre-upgrade backup through Upgrading, keeping API_KEY_SECRET and JWT_SECRET stable. A newer database schema can prevent an older build from opening it.

Verify providers, keys, combos, and usage after restore, then test a request.

Logs

Read docker logs durindoor for a container, or start the CLI with --log. Dashboard Console log is an in-memory ring cleared on restart. Optional MITM dumps live under DATA_DIR/logs/mitm; ENABLE_REQUEST_LOGS=true writes metadata under logs/ in the process working directory.

Keep optional request logging to a short diagnostic window. Match a failure to its request ID and timestamp, then verify the original request after applying the fix.

On this page

Edit on GitHub