Client or integration
Claude Code
Provider or upstream service
OpenCodex multi-provider routing for Claude Code internal Auto Mode permission-classifier requests. Reproduced with:
- an explicitly selected Claude-compatible relay for the main model; and
- DeepSeek configured as the global
defaultProvider.
This is not an upstream DeepSeek defect: DeepSeek correctly rejects the Claude model slug it should never have received.
OpenCodex version
2.11.1
Environment:
- Claude Code
2.1.228
- macOS
26.5.2
- OpenCodex Claude proxy mode
- Claude Code Auto permission mode
- inbound endpoint:
POST /v1/messages
Endpoint or capability
POST /v1/messages — Claude Code Auto Mode background permission classifier used before Bash, Agent/subagent, Workflow, and other tool actions.
Current behaviour
A Claude Code session can explicitly run its main model through a routed provider, for example:
The main-model requests resolve to RelayA and return HTTP 200.
Before Claude Code executes a tool in Auto Mode, it sends a separate internal classifier request whose model is the bare slug:
That side request does not preserve the main session's provider namespace. With no explicit claudeCode.modelMap entry, OpenCodex passes the bare slug through and routeModel() sends it to the global defaultProvider.
If the global default is DeepSeek, the observed route is:
requestedModel: claude-opus-5
routeKind: default-provider
selected.provider: deepseek
selected.model: claude-opus-5
selected.reason: default-provider
DeepSeek then correctly rejects the unsupported model with HTTP 400. Claude Code converts that failure into the misleading user-facing message:
claude-opus-5 is temporarily unavailable, so auto mode cannot
determine the safety of Bash/Agent/Workflow right now.
Consequences:
- the explicitly routed main model continues answering normally;
- Bash, Agent/subagent, Workflow, and other classified actions are blocked;
- retries repeat the same incorrect route;
- users can easily misdiagnose this as an Anthropic/Claude outage, a broken script, or a subagent configuration problem;
- changing the featured subagent roster does not fix it because the permission classifier is a separate internal request path.
On this machine, the same deterministic mismatch produced 71 HTTP 400 requests between 2026-08-07 and 2026-08-14. It affected multiple Claude Code conversations and multiple tool types.
The current static workaround is:
claudeCode.modelMap["claude-opus-5"] = "RelayA/claude-opus-5"
However, this is not sufficient as the default integration behaviour:
- It is not created automatically when the user selects
RelayA/claude-fable-5.
- It becomes stale when the user changes the main provider in Claude Code's model picker.
- It has no health-aware or configured fallback.
- The GUI does not make the hidden classifier dependency and its resolved provider obvious.
- A static cross-provider map can send command/context metadata to a provider different from the main provider without making that boundary visible.
The relevant current flow appears to be:
src/claude/inbound.ts::resolveInboundModel(): alias → Desktop alias → static modelMap → bare passthrough.
src/router.ts::routeModel(): an otherwise unresolved bare Claude slug falls through to config.defaultProvider.
Expected behaviour
Claude Code internal classifier side requests should not silently lose provider affinity and fall through to an incompatible global default.
Recommended resolution order:
- Same-provider affinity: when the current Claude Code conversation/session is explicitly routed through
RelayA/claude-fable-5, resolve a bare classifier request for claude-opus-5 to RelayA/claude-opus-5 if that model is available and enabled there.
- Explicit classifier configuration: support a clear setting such as
claudeCode.classifierModel and/or ordered claudeCode.classifierFallbacks.
- Opt-in compatible fallback: if the current provider lacks Opus 5, try only user-approved fallback providers in deterministic order. Cross-provider fallback should be explicit because it affects privacy, billing, and data boundaries.
- Fail closed with a truthful error: if no compatible classifier route exists, return an actionable message such as:
No enabled provider can serve the Claude Code Auto Mode classifier
model claude-opus-5. Configure claudeCode.classifierModel or disable Auto Mode.
- Never silently send a Claude-family classifier slug to a provider whose catalog/model family is incompatible.
- Expose classifier routing in diagnostics/UI: requested model, resolved provider/model, affinity source, fallback reason, and availability.
A static modelMap should remain a valid explicit override, but users should not need to discover and hand-maintain it merely to make Claude Code's built-in safety path work after selecting a routed model.
Minimal redacted request or reproduction
# 1. Configure two enabled providers (all URLs and credentials omitted):
# - defaultProvider = deepseek
# - RelayA exposes claude-fable-5 and claude-opus-5
#
# 2. Enable Claude Code proxy integration and leave claudeCode.modelMap empty.
ocx claude
# 3. In Claude Code, select the routed main model:
# RelayA/claude-fable-5
#
# 4. Switch to Auto permission mode.
#
# 5. Ask Claude Code to perform either action:
# - run Bash: pwd
# - launch an Agent/subagent that replies WORKER_OK
#
# 6. Observe:
# - RelayA/claude-fable-5 main requests return 200
# - the internal bare claude-opus-5 request routes to deepseek
# - the classifier request returns 400
# - Bash/Agent is blocked as "classifier temporarily unavailable"
Actual response or error
# Redacted OpenCodex usage/route evidence
{
"provider": "deepseek",
"model": "claude-opus-5",
"requestedModel": "claude-opus-5",
"status": 400,
"errorCode": "invalid_request_error",
"routeDecision": {
"requestedModel": "claude-opus-5",
"routeKind": "default-provider",
"selected": {
"provider": "deepseek",
"model": "claude-opus-5",
"reason": "default-provider"
}
},
"upstreamError": "The supported API model names are deepseek-v4-pro or deepseek-v4-flash, but you passed claude-opus-5."
}
# Claude Code tool result
claude-opus-5 is temporarily unavailable, so auto mode cannot
determine the safety of Agent right now. Wait briefly and try again.
The same error is reproducible for Bash and Workflow. No API keys, provider URLs, prompts, source code, or account identifiers are included above.
Upstream documentation
Claude Code permission modes document that Auto Mode performs background safety checks before tool actions:
https://code.claude.com/docs/en/permissions#permission-modes
There is no public specification for the exact internal classifier model slug or its side-request routing contract. The concrete client requirement is observable from Claude Code 2.1.228: it sends a separate /v1/messages request with the bare claude-opus-5 slug and blocks the tool when that request fails.
Suggested mapping or implementation notes
Potential implementation directions:
- Track the most recent explicit main-model provider for the Claude Code conversation/session and use it as classifier affinity.
- Recognize the classifier request using stable Claude Code request metadata/shape rather than treating every user-selected bare Claude model as an internal classifier.
- Add validated config:
{
"claudeCode": {
"classifierModel": "RelayA/claude-opus-5",
"classifierFallbacks": [
"RelayB/claude-opus-5"
]
}
}
- Validate candidates against enabled providers and configured/live catalogs before selection.
- Preserve
modelMap as the highest-priority explicit override.
- Add a route-decision kind such as
claude-classifier-affinity or claude-classifier-fallback.
- Include a regression test where:
- main model =
RelayA/claude-fable-5;
- bare classifier model =
claude-opus-5;
- global default = DeepSeek;
- expected classifier route =
RelayA/claude-opus-5, never DeepSeek.
- Add a negative test where no approved provider exposes Opus 5 and assert a clear, actionable fail-closed error.
Additional context and attachments
Related but distinct:
Impact assessment: high. A single hidden routing mismatch disables the core agentic capabilities of Claude Code in Auto Mode while leaving ordinary main-model chat apparently healthy, producing a confusing split-brain failure that can persist across conversations.
Checks
Client or integration
Claude Code
Provider or upstream service
OpenCodex multi-provider routing for Claude Code internal Auto Mode permission-classifier requests. Reproduced with:
defaultProvider.This is not an upstream DeepSeek defect: DeepSeek correctly rejects the Claude model slug it should never have received.
OpenCodex version
2.11.1Environment:
2.1.22826.5.2POST /v1/messagesEndpoint or capability
POST /v1/messages— Claude Code Auto Mode background permission classifier used before Bash, Agent/subagent, Workflow, and other tool actions.Current behaviour
A Claude Code session can explicitly run its main model through a routed provider, for example:
The main-model requests resolve to
RelayAand return HTTP 200.Before Claude Code executes a tool in Auto Mode, it sends a separate internal classifier request whose model is the bare slug:
That side request does not preserve the main session's provider namespace. With no explicit
claudeCode.modelMapentry, OpenCodex passes the bare slug through androuteModel()sends it to the globaldefaultProvider.If the global default is DeepSeek, the observed route is:
DeepSeek then correctly rejects the unsupported model with HTTP 400. Claude Code converts that failure into the misleading user-facing message:
Consequences:
On this machine, the same deterministic mismatch produced 71 HTTP 400 requests between 2026-08-07 and 2026-08-14. It affected multiple Claude Code conversations and multiple tool types.
The current static workaround is:
However, this is not sufficient as the default integration behaviour:
RelayA/claude-fable-5.The relevant current flow appears to be:
src/claude/inbound.ts::resolveInboundModel(): alias → Desktop alias → staticmodelMap→ bare passthrough.src/router.ts::routeModel(): an otherwise unresolved bare Claude slug falls through toconfig.defaultProvider.Expected behaviour
Claude Code internal classifier side requests should not silently lose provider affinity and fall through to an incompatible global default.
Recommended resolution order:
RelayA/claude-fable-5, resolve a bare classifier request forclaude-opus-5toRelayA/claude-opus-5if that model is available and enabled there.claudeCode.classifierModeland/or orderedclaudeCode.classifierFallbacks.A static
modelMapshould remain a valid explicit override, but users should not need to discover and hand-maintain it merely to make Claude Code's built-in safety path work after selecting a routed model.Minimal redacted request or reproduction
Actual response or error
The same error is reproducible for Bash and Workflow. No API keys, provider URLs, prompts, source code, or account identifiers are included above.
Upstream documentation
Claude Code permission modes document that Auto Mode performs background safety checks before tool actions:
https://code.claude.com/docs/en/permissions#permission-modes
There is no public specification for the exact internal classifier model slug or its side-request routing contract. The concrete client requirement is observable from Claude Code
2.1.228: it sends a separate/v1/messagesrequest with the bareclaude-opus-5slug and blocks the tool when that request fails.Suggested mapping or implementation notes
Potential implementation directions:
{ "claudeCode": { "classifierModel": "RelayA/claude-opus-5", "classifierFallbacks": [ "RelayB/claude-opus-5" ] } }modelMapas the highest-priority explicit override.claude-classifier-affinityorclaude-classifier-fallback.RelayA/claude-fable-5;claude-opus-5;RelayA/claude-opus-5, never DeepSeek.Additional context and attachments
Related but distinct:
Impact assessment: high. A single hidden routing mismatch disables the core agentic capabilities of Claude Code in Auto Mode while leaving ordinary main-model chat apparently healthy, producing a confusing split-brain failure that can persist across conversations.
Checks