Responses Proxy troubleshooting
Start with health, admin auth, routing state, and a registered alias before debugging deeper provider behavior.
Quick health checklist
Set the service URL and admin header:
export RESPONSES_PROXY_BASE_URL="https://agent-responses-proxy.svc.cloud.morph.so"
export ADMIN_HEADER="Authorization: Bearer $SEELE_ORG_ADMIN_MORPH_API_KEY"
export RESPONSES_PROXY_API_KEY="rprx-..."
Check public health and OpenAPI:
curl -fsS "$RESPONSES_PROXY_BASE_URL/healthz"
curl -fsS "$RESPONSES_PROXY_BASE_URL/readyz"
curl -fsS "$RESPONSES_PROXY_BASE_URL/openapi.json" >/tmp/openapi.json
Check authenticated admin state:
curl -fsS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/state"
curl -fsS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/routing"
Send a minimal request with a registered alias:
curl -sS "$RESPONSES_PROXY_BASE_URL/v1/responses" \
-H "Authorization: Bearer $RESPONSES_PROXY_API_KEY" \
-H 'content-type: application/json' \
-d '{
"model": "openai/gpt-mini",
"input": "Reply with exactly: smoke ok",
"metadata": {
"session_id": "troubleshooting-smoke-1",
"session_name": "troubleshooting smoke"
}
}'
401 on /v1/responses
Cause: the request is missing a valid org-scoped proxy key.
Fix: mint a key in the Responses console or with POST /admin/keys, then send the returned secret as:
Authorization: Bearer <key>
Keys are org-scoped, so the key must belong to the org that owns the target provider and alias.
503 on /v1/responses
Cause: the registry has no routable providers, or all compatible providers were skipped.
Fix:
- Register a provider with
wire_apiset toresponses,chat_completions, oranthropic_messages. - Ensure the provider is enabled.
- Ensure inline credentials can decrypt, or the referenced environment value exists.
- Confirm
resolved_key_countandpool_sizeare greater than zero. - Register a model alias pointing at the provider.
- Check the routing view:
curl -sS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/routing"
400 missing model
POST /v1/responses create requests require model. Use a registered alias, for example:
{"model": "openai/gpt-mini", "input": "hello"}
404 unknown model
The model value is not a registered alias. Check aliases:
curl -sS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/models"
Admin returns 401
Cause: missing or invalid Morph org token or service delegation token.
Fix:
curl -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/providers"
If this fails through the console, confirm the frontend server can obtain a service delegation token for agent-responses-proxy with responses:admin.
Admin returns 403
Cause: the bearer token is valid, but the principal is not a member of the allowed organization or does not have an allowed role.
Fix: check PROXY_ADMIN_ALLOWED_ORG_ID, PROXY_ADMIN_ALLOWED_ORG_SLUG, and PROXY_ADMIN_ALLOWED_ROLES.
Admin returns 503
Cause: admin auth is misconfigured or a Morph auth dependency is unavailable.
Fix: check PROXY_ADMIN_AUTH_MODE, Morph API base URLs, PROXY_ADMIN_DELEGATION_SERVICE_API_KEY, and allowed org settings.
Records disappear after redeploy
Usually the service is using container-local SQLite, or you were viewing Redis live state.
Fix:
- Set
PROXY_DATABASE_URLto the same Neon/Postgres database for every deploy. - Confirm API and worker receive the same database URL.
- Expect Redis counters, health, queue depth, and affinity to reset with container lifecycle.
Requests succeed but metrics are empty
Likely causes:
PROXY_DATABASE_URLis unset, so the service is using local SQLite- API and frontend/BFF are pointing at different deployments or organizations
- the request has not completed yet; streaming rows are written when the stream closes
- the request used a proxy key from a different org than the dashboard org
Fix:
curl -sS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/overview?limit=5"
curl -sS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/requests?limit=5"
curl -sS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/metrics"
Current backend builds write request and decision rows inline on the request path, then publish raw telemetry to Redis for worker persistence and replay. If /admin/requests is empty after a completed request, check the database URL, proxy-key org, and active frontend org before debugging the worker.
If telemetry_queue_depth grows while request rows are fresh, the data plane is working but the worker is lagging on raw telemetry events. Restart the worker or inspect worker logs.
Provider is listed but does not route
Check:
enabledis truewire_apiisresponses,chat_completions, oranthropic_messagescredential_resolvedis trueresolved_key_countandpool_sizeare greater than zero- Env-sourced credentials are enabled for
PROXY_BOOTSTRAP_ORG_ID, or for an org listed inPROXY_ENV_CREDENTIAL_OWNER_ORG_IDS - Production provider URLs are HTTPS public hosts
- model alias has
provider_idset to that provider /admin/routingincludes the alias and resource
Use validation endpoints before trying Codex:
curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/providers/test" \
-H "$ADMIN_HEADER" \
-H 'content-type: application/json' \
-d '{"id":"openai-main","name":"OpenAI","provider_type":"openai","base_url":"https://api.openai.com/v1","wire_api":"responses","auth_type":"bearer","api_key":"replace-with-provider-key"}'
curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/models/test" \
-H "$ADMIN_HEADER" \
-H 'content-type: application/json' \
-d '{"provider_id":"openai-main","upstream_id":"gpt-4.1-mini"}'
Inline credential rejected
Cause: inline provider credentials require PROXY_SECRET_ENCRYPTION_KEY.
Fix: set the encryption value, restart, and retry. Keep it stable across deploys.
Secret reference does not resolve
Cause: a provider uses credential_ref=env:NAME, but NAME is not present in the proxy runtime environment.
Fix: add the variable to the proxy deployment. In the console, $NAME is validated against org secrets by the BFF, but the backend can only resolve it if the runtime env contains the same name, matches *_API_KEY or *_API_KEY_<number> unless allowlisted, and is not a blocked sensitive name.
Token columns are empty
Token usage is extracted only when response bodies include supported usage fields:
usage.input_tokensusage.output_tokensusage.total_tokensusage.prompt_tokensusage.completion_tokens- SSE payloads with
response.usage
If usage is absent or malformed, token columns remain empty. Request and response body columns may also be empty because PROXY_CAPTURE_REQUEST_BODY and PROXY_CAPTURE_RESPONSE_BODY default to false.
For streaming responses, token usage is written after the stream closes. OpenAI Responses-backed Claude Code streaming also records usage from the converted stream state when available.
Revoked proxy key still works briefly
Cause: positive proxy-key org resolution is cached in each running API process for PROXY_PROXY_KEY_CACHE_TTL_SECONDS, which defaults to 30 seconds.
Fix: wait for the cache window to expire, restart the API process, or lower the TTL. Set PROXY_PROXY_KEY_CACHE_TTL_SECONDS=0 only when immediate revocation matters more than hot-path efficiency. Invalid keys are cached for at most 5 seconds.
Metrics update but raw telemetry queue grows
Cause: request and decision rows are persisted inline, but Redis stream events still need the telemetry worker for raw event history and replay.
Fix:
uv run responses-proxy-worker
curl -sS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/overview?limit=5"
Check worker logs for Redis group, database, or schema errors. API and worker should share PROXY_REDIS_URL and PROXY_DATABASE_URL.
Codex model metadata warning
If Codex prints Model metadata for <alias> not found, the proxy alias can still work, but Codex does not have local catalog metadata for that alias.
Add the alias to codex-model-catalog.json, or point MODEL_CATALOG_JSON at a custom catalog. For one-off tuning, set model-specific limits to match the upstream model:
MODEL_CONTEXT_WINDOW=200000
MODEL_AUTO_COMPACT_TOKEN_LIMIT=160000
TOOL_OUTPUT_TOKEN_LIMIT=12000
Codex posts to /responses
If Codex reports unexpected status 404 Not Found for /responses, the configured provider base URL is missing /v1.
Use:
https://agent-responses-proxy.svc.cloud.morph.so/v1
Claude Code posts to /v1/v1/messages
If server logs show POST /v1/v1/messages, ANTHROPIC_BASE_URL already includes /v1 and Claude Code is appending another /v1.
Use the proxy base URL that matches your Claude Code version. Current tested setups use:
export ANTHROPIC_BASE_URL="https://agent-responses-proxy.svc.cloud.morph.so/v1"
If that produces /v1/v1/messages, remove the suffix:
export ANTHROPIC_BASE_URL="https://agent-responses-proxy.svc.cloud.morph.so"
Claude Code returns 401 asking for login
Cause: the proxy key is missing from the configured Claude Code custom headers.
Fix:
export ANTHROPIC_CUSTOM_HEADERS="X-Responses-Proxy-Key: rprx-..."
The proxy key is not an Anthropic key. It must be minted from the Responses Proxy dashboard or /admin/keys.
Claude Code says model may not exist
Likely causes:
- the model name is not registered as an alias in the proxy-key org
GET /v1/modelsdoes not list the alias- the alias points at a provider protocol unsupported by the Claude Code gateway
- the provider has no routable credential rows or fallback credentials
Fix:
curl -sS "$RESPONSES_PROXY_BASE_URL/v1/models" \
-H "X-Responses-Proxy-Key: $RESPONSES_PROXY_API_KEY"
curl -sS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/routing"
Claude Code aliases support wire_api="anthropic_messages", wire_api="responses", and wire_api="chat_completions" providers. Chat aliases also require PROXY_CLAUDE_GATEWAY_CHAT_COMPLETIONS_ENABLED=true. Check capabilities.claude_code.enabled and ensure an explicit profile matches the provider protocol.
Claude Code alias is missing from /v1/models
Check these conditions:
- the alias and provider are enabled and belong to the proxy key's org
- the provider has a resolved credential and active resource
capabilities.claude_code.enabledis notfalseprofileisautoor matches the providerwire_api- Chat Completions routing is enabled by deployment
Existing aliases without a claude_code object use the compatibility defaults and are eligible. Use the console alias editor to make the intended capabilities explicit.
Claude Code request returns 413
The complete JSON request, including base64 media expansion, exceeds PROXY_CLAUDE_GATEWAY_MAX_BODY_BYTES. The default is 33554432 bytes.
Reduce or resize attached media, split the request, or raise the limit deliberately. URL images avoid base64 expansion; Chat Completions document URLs are not portable and remain unsupported.
Claude Code media or tool request returns 422
The gateway rejects capabilities it cannot preserve. Common causes:
- images, documents, or client tools are disabled on the alias
- a Claude image uses a provider file id instead of base64 or URL input
- a document uses a provider-scoped file id
- a Chat Completions alias receives a document URL instead of base64 file data
- a translated request contains a server tool other than supported web search
- hosted web search is not enabled on a Responses alias, or is configured on Chat Completions
Do not enable a toggle merely to suppress the error. Confirm the upstream model and provider implement that input shape, then test the complete flow.
X-Responses-Proxy-Normalized-Fields appears
For translated Responses and Chat aliases, this response header lists Anthropic-only fields the gateway removed or normalized, including prompt-cache, context-management, top_k, and tool-level cache/eager-streaming hints. It is diagnostic, not an upstream error.
Use an anthropic_native alias when the workflow requires provider-native Messages semantics. Translated aliases intentionally reject or report fields that cannot be represented exactly.
Claude Code OAuth setup fails
Check:
PROXY_ENABLE_CLAUDE_CODE_TOKEN_ESCROW=truePROXY_SECRET_ENCRYPTION_KEYis stable and configured- the provider is first-party Anthropic:
provider_type="anthropic"andbase_url="https://api.anthropic.com/v1" - the provider uses
wire_api="anthropic_messages",auth_type="bearer", andauth_header="authorization" - the setup token has not expired or already completed
Use stdin for setup tokens:
printf '%s\n' "$SETUP_TOKEN" | responses-proxy-claude-code teleport \
--base-url "$RESPONSES_PROXY_BASE_URL" \
--session-id "$SESSION_ID" \
--setup-token-stdin
In fish, use the printf ... | form rather than bash-style heredocs.
Tool adapter errors
If a Chat Completions or Anthropic model reports that a tool type is unsupported, confirm the backend build includes Codex tool adapter support.
Current adapter behavior:
- Codex
customtools map to upstream JSON tools with aninputstring. - Freeform
apply_patchtool arguments tolerate keys such asinput,patch,diff,content,cmd, andcommand. - Upstream calls are converted back to Responses
custom_tool_callitems. - Nested
namespacetool containers are flattened into provider-native JSON tools. - Non-forwardable hosted Responses tools, such as
web_search, are omitted for Chat Completions and Anthropic providers.
That omission rule applies to the Codex-facing /v1/responses adapter. On the Claude Code surface, translated server tools are validated strictly: only web_search can map to a Responses alias, and Chat Completions has no hosted-tool mapping. Unsupported server tools return an error instead of being omitted.
Docker health check fails
Diagnose:
docker ps -a
docker logs --tail 200 agent-responses-proxy-server
curl -fsS "$RESPONSES_PROXY_BASE_URL/healthz"
Common causes: Redis failed to start, port 8000 is in use, or the API/worker exited.
GitHub Actions deploy fails
The deploy dry-run requires Morph builder settings plus:
PROXY_DATABASE_URLPROXY_BOOTSTRAP_ORG_IDPROXY_ADMIN_DELEGATION_SERVICE_API_KEYwhen using delegation authPROXY_ADMIN_ALLOWED_ORG_IDorPROXY_ADMIN_ALLOWED_ORG_SLUGwhen using org authPROXY_SECRET_ENCRYPTION_KEY
Add them as GitHub environment secrets without printing values in logs.