Troubleshooting¶
Desktop app install¶
The desktop builds are not notarized (macOS) or code-signed (Windows), so the operating system may warn when you install them by hand. The one-line installers in the README avoid these prompts on macOS and Linux.
- macOS says "AIRelays is damaged" or "cannot be verified" after you downloaded the DMG in a browser: clear the quarantine flag, then open the app again.
xattr -dr com.apple.quarantine /Applications/AIRelays.app
Alternatively, Control-click the app in Finder and choose Open.
- The installer reports that no desktop installer exists for your system:
desktop builds cover Apple Silicon Macs, x86_64 Linux, and x64 Windows.
On other machines, including Intel Macs, use the headless installer
(scripts/install-headless.sh); it runs the same relay without the tray.
- Windows SmartScreen blocks the setup .exe: choose More info →
Run anyway, or use the PowerShell installer.
- Linux: the AppImage does not start from a terminal with a FUSE error:
install libfuse2 (libfuse2t64 on Ubuntu 24.04), or run it with
APPIMAGE_EXTRACT_AND_RUN=1. The menu entry created by the installer
already does this when FUSE 2 is missing. GNOME needs the AppIndicator
extension to show the tray icon.
- The headless installer warns that ~/.local/bin is not on your PATH:
add export PATH="$HOME/.local/bin:$PATH" to your shell profile and open
a new terminal.
Traffic logs report a cleanup error¶
The tray Settings page or GET /v1/relay/logging can report last_error, or
airelays logs can print a cleanup error. AIRelays pauses traffic-log writes
until it can enforce the configured limit; relay requests continue normally.
Check that the configured log directory is writable and has space available:
airelays logs
airelays status --json
If the limit is smaller than the history you need, archive the relevant log files first, then raise the total limit or shorten retention:
airelays logs --retention-days 30 --max-total-mb 2048 --max-file-mb 50
Use the same --config, --data-dir, or --logs-dir arguments as the
running relay. Apply the policy again, then confirm that last_error is empty
and over_budget is false. See traffic-log retention
for the policy, ownership boundaries, and permanent-deletion behavior.
A model is missing or a Claude alias is unclear¶
Use the Models tab's Refresh button or GET /v1/models?refresh=true to
reload both provider catalogs. OpenAI discovery uses client_version =
"auto" by default; an explicit older version pin can select an older
catalog. Update the installed Codex CLI to follow newer catalogs.
For Claude, update the configured claude CLI and inspect the alias's
airelays.resolved_model in /v1/models. Concrete ids are also listed.
If discovery fails, inspect providers.claude.models_discovery_error in
GET /v1/relay/status; configured ids and the last successful catalog
remain available. See model discovery and
configuration.
401 Missing or invalid AIRelays bearer token¶
- run
airelays status - run
airelays doctor --skip-response - confirm the relay token is present
- confirm the client is calling
http://HOST:PORT/v1/... - use
airelays token showif needed
503 No ChatGPT login found¶
- run
airelays status - run
airelays doctor --skip-response - if the OpenAI runtime is enabled, run
airelays login - on a server or over SSH, run
airelays login --device - if the browser flow cannot bind
localhost:1455, useairelays login --device
I opened the login URL on my laptop and got a connection error at localhost:1455¶
The browser flow's sign-in redirect goes to localhost:1455 on the machine
running the browser — pasting the URL into a browser on another computer
sends the redirect to the wrong machine, so the login on the server never
completes and eventually times out.
Two fixes:
- Device-code login (recommended):
airelays login --deviceprints a short code you approve from a browser on any device. This is the default on SSH sessions and displayless Linux. - SSH tunnel (if you specifically need the full browser flow, e.g. for a
browser-profile picker): run
ssh -L 1455:localhost:1455 user@serverfirst, then open the printed URL in your local browser. The tunnel can be opened even afterairelays loginhas started waiting.
Claude runtime is "not ready" under systemd/docker even though claude setup-token worked¶
A shell export CLAUDE_CODE_OAUTH_TOKEN=... never reaches a service
manager's environment, and it evaporates on reboot. Store the token instead:
airelays claude set-token # paste the token from `claude setup-token`
It is written 0600 to ~/.airelays/claude-token and injected into every
claude invocation automatically. airelays status shows the token source
(file, env, or none) under the Claude provider.
Claude requests fail even though claude auth login succeeded¶
A stored token (from airelays claude set-token) overrides the claude
CLI's own sign-in for relay requests. If that stored token is stale, relay
requests keep failing no matter how often you sign in through the CLI.
Checks and fix:
airelays statusshows the Claude token source;filemeans a stored token is in effect- remove it with
airelays claude logout(also signs the CLI out) or, in the desktop app, open the Claude token dialog and use "Remove stored token" (keeps the CLI sign-in) - verify with a
claude:*test request orairelays doctor
Desktop app shows "Sign-in expired" on an OpenAI account¶
OpenAI ended that account's stored session (for example after signing in to Codex elsewhere), so the relay can no longer refresh its token. The account is out of rotation until you sign in again.
- click Sign in again next to the badge and pick the same account in the browser — the stored sign-in is replaced in place, no sign-out needed
- the usage bars return within a few seconds of a successful sign-in
- the raw upstream error is available on hover over the note under the row
- from the CLI,
airelays logindoes the same repair: signing in with an already-enrolled account refreshes that account's stored credentials
Desktop app shows "Running — not responding"¶
The relay process is alive but did not answer the app's health probe — usually heavy system load or a long request burst.
- it recovers on its own once the relay answers again; the label flips back to "Running"
- if it persists, open the Console tab for relay output, or use Restart
- Stop/Restart keep working: the app still manages the process
422 on Claude routes¶
The Claude runtime supports discovered aliases and concrete model ids, plus configured overrides, on text chat.completions and text completions.
Checks:
- confirm the model id appears in
/v1/models, as a discovered alias, concreteclaude-*id, or configured Claude override - remove tools, files, images, audio, and
conversation(response_formatjson_schema/json_object is supported on chat completions) - remove unsupported generation controls
Cursor custom OpenAI endpoint errors¶
If Cursor is configured to call AIRelays as a custom OpenAI-compatible endpoint and Agent/Edit flows fail, first identify which malformed request family you are seeing.
AIRelays 0.12.2 and later accepts the two Cursor request shapes reported publicly by Cursor users and staff on February 26, 2026 and July 9, 2026:
- full Responses-style bodies sent to
/v1/chat/completions - flat Responses-style
customtools / tool choices / tool calls on the chat route (for exampleApplyPatch)
AIRelays 0.12.3 and later also strips unsupported top-level caller
identity fields (user, safety_identifier) before the upstream request
and rejects OpenAI model ids that are absent from a working live
/v1/models catalog, instead of letting those failures happen upstream.
Checks:
- if you see
Only function tools are currently supported on chat routes., upgrade AIRelays - if you see
Unsupported parameter: user, upgrade AIRelays; current versions stripuser(andsafety_identifier) locally because the ChatGPT/Codex backend does not accept caller-supplied end-user ids - if you see
The '...model...' model is not supported when using Codex with a ChatGPT account., compare the chosen model againstGET /v1/models; with a working upstream catalog AIRelays now rejects unsupported OpenAI ids locally and only[providers.openai].extra_modelsshould bypass that check. On multi-account relays, if you just added or removed an account, current versions refresh that admission cache automatically before deciding - inspect the traffic log under
~/.airelays/logs/...for acompatibility_adaptationrecord showing the chat route accepted a Responses-shaped body - if the request mixes both
messagesandinput, AIRelays rejects it loudly; that payload is ambiguous and must be fixed at the client - if a
role:"tool"message does not reference a preceding assistant tool call in the same request, AIRelays rejects it as malformed instead of guessing the wrong tool type - if Cursor still fails after the normalization above, capture the inbound request body from the traffic log and compare it with the documented supported shapes in docs/api.md
Claude startup refusal¶
When the Claude runtime is enabled:
- keep the listener on
127.0.0.1,localhost, or::1 - keep relay bearer auth enabled
- keep
trust_x_forwarded_for=false
429 Too many invalid authentication attempts from this IP¶
- wait for the
Retry-Afterwindow - update the client to the correct relay token
- rotate the token if needed
413 on uploads¶
- confirm the file is below the per-file upload ceiling
- confirm the relay has not reached the total stored-upload quota
502 / 429 with server_is_overloaded or usage_limit_reached, or slow answers while the upstream is degraded¶
- the upstream itself is failing or out of quota; the error body carries the upstream's own code and message
- before surfacing the error, the relay retried automatically (default 3
retries waiting 5s/20s/60s, each re-running account failover), which is why
a failing request can take a minute or more before answering — check the
traffic log for
retry_backoff/retry_skipped/upstream_stream_errorrecords to see what happened - tune or disable with
retry_attempts/retry_backoff_seconds([providers.openai], or desktop Settings → Providers) - a
429that names a reset far in the future is not retried (waiting a minute cannot help a window that resets in hours); with several accounts enrolled, the message reports the earliest account recovery - a
400invalid_request_error(for example an input that exceeds the model's context window) is answered immediately with the upstream's own error: it is deterministic, so it is never retried, never rotated to another account, and never benches an account — fix the request instead - "All N OpenAI accounts are at their limits" is only claimed when every account is benched by real limit evidence (a quota rejection or the usage report); rounds of transient upstream failures answer "All N OpenAI accounts failed for this request" instead
Live upstream verification¶
Use airelays doctor when local state looks correct but client requests still
fail. It checks local setup, then verifies the OpenAI upstream /models route
and runs a tiny /responses smoke request when the OpenAI runtime is enabled
and logged in.
airelays doctor
Use airelays doctor --skip-response when you want setup and model-list checks
without sending a generation request.