Skip to content

[Provider compatibility] Claude Code Auto Mode classifier loses provider affinity and routes bare claude-opus-5 to an incompatible default provider #1697

Description

@str0203

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:

RelayA/claude-fable-5

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:

claude-opus-5

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:

  1. It is not created automatically when the user selects RelayA/claude-fable-5.
  2. It becomes stale when the user changes the main provider in Claude Code's model picker.
  3. It has no health-aware or configured fallback.
  4. The GUI does not make the hidden classifier dependency and its resolved provider obvious.
  5. 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:

  1. 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.
  2. Explicit classifier configuration: support a clear setting such as claudeCode.classifierModel and/or ordered claudeCode.classifierFallbacks.
  3. 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.
  4. 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.
    
  5. Never silently send a Claude-family classifier slug to a provider whose catalog/model family is incompatible.
  6. 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

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reports

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions