Skip to content

[Bug]: AgentRouter openai-chat providers need a Codex client fingerprint; non-English fix only covers anthropic #2717

Closed

Description

@rrmlima

Client or integration

Codex CLI (also reproducible with a direct HTTP client).

Provider or upstream service

AgentRouter (agentrouter.org)

Version

OpenCodex 2.33.0.

Operating system

Linux server (x86_64).

Provider and model

AgentRouter / claude-opus-5.

OpenCodex version

2.33.0, official npm package; affected adapter files verified byte-for-byte against the published tarball.

Endpoint or capability

/v1/responses translated to an AgentRouter openai-chat provider at https://agentrouter.org/v1; client authentication/fingerprint and non-English prompts.

Summary

AgentRouter openai-chat providers fail with 401 unless a recognized Codex client fingerprint is manually configured, and minimal Portuguese requests still fail with 400 content-blocked because the 2.31 fix covers only the Anthropic adapter.

Current behaviour

Two independent failures remain after the AgentRouter language fix merged in #2162:

  1. A valid AgentRouter key on a normal openai-chat provider is rejected with HTTP 401 unauthorized_client_error unless the operator manually adds an undocumented recognized-client fingerprint (User-Agent or originator) to provider.headers.
  2. Once fingerprint authentication succeeds, a minimal Portuguese request still returns HTTP 400 content-blocked. The same provider works from the real Codex CLI because Codex supplies a large English harness context before the Portuguese user prompt.

OpenCodex 2.33.0 implements AgentRouter language framing only in src/adapters/anthropic.ts. src/adapters/openai-chat.ts has no AgentRouter-specific host detection, fingerprint defaults, or language framing.

Controlled header matrix, same key/model/body/process:

Provider headers Minimal EN request Real Codex PT request
none 401 unauthorized client 401 after five retries
version: 0.149.1 only 401 not run
User-Agent: codex_cli_rs/0.149.1 only 200 200
originator: codex_cli_rs only 200 200
UA + originator + version 0.149.1 200 200
UA + originator + version 0.145.0-alpha.30 200 200

The old version is not required; the recognized Codex identity is what matters.

Expected behaviour

A first-class AgentRouter provider/preset using openai-chat should work with a valid key without requiring operators to discover and copy undocumented magic headers.

OpenCodex should either:

  • supply a documented, stable AgentRouter-compatible client fingerprint by default for exact agentrouter.org hosts; or
  • expose a documented clientFingerprint compatibility option and diagnose the upstream 401 clearly.

AgentRouter's non-English request policy should also be applied consistently to both the Anthropic and OpenAI Chat adapter paths, including translated /v1/responses and raw /v1/chat/completions passthrough.

Operator-specified headers must continue to override defaults, and non-AgentRouter providers must remain unchanged.

Reproduction

The exact minimal reproduction is provided below.

Minimal redacted request or reproduction

# 1. Configure a provider (valid key redacted):
# providers.agentrouter-test = {
#   adapter: "openai-chat",
#   baseUrl: "https://agentrouter.org/v1",
#   authMode: "key",
#   apiKey: "<redacted>",
#   liveModels: false,
#   models: ["claude-opus-5"],
#   selectedModels: ["claude-opus-5"],
#   defaultModel: "claude-opus-5"
# }

# 2. Send an English request with no custom provider headers:
curl -sS http://127.0.0.1:10100/v1/responses \
  -H "Authorization: Bearer $OPENCODEX_API_AUTH_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{
    "model":"agentrouter-test/claude-opus-5",
    "input":"Reply with exactly: OK",
    "stream":false,
    "store":false,
    "max_output_tokens":64
  }'
# => HTTP 401 unauthorized client

# 3. Add either header and repeat:
# headers: { "User-Agent": "codex_cli_rs/0.149.1" }
# OR
# headers: { "originator": "codex_cli_rs" }
# => HTTP 200

# 4. With the recognized fingerprint, change input to Portuguese:
# "input":"Responda somente com: OK"
# => minimal direct request: HTTP 400 content-blocked

# 5. Run through the real Codex CLI, which supplies English harness context:
codex exec --ephemeral --skip-git-repo-check \
  -m agentrouter-test/claude-opus-5 \
  'Responda somente com: OK'
# => HTTP 200 / exact answer

Actual response or error

# Without recognized client fingerprint:
HTTP 401
{
  "error": {
    "message": "unauthorized client detected, contact support for assistance"
  },
  "type": "unauthorized_client_error"
}

# With recognized fingerprint, minimal Portuguese request:
HTTP 400
{
  "error": {
    "code": "content-blocked",
    "type": "agent_router_api_error"
  }
}

# With recognized fingerprint, real Codex CLI Portuguese request:
HTTP 200
turn.completed

Request IDs, credentials, account details, and host-specific data were removed.

Upstream documentation

AgentRouter does not appear to publish a public specification documenting the required User-Agent/originator client allowlist or the non-English content-scoring contract. The observable behavior above is based on controlled black-box compatibility testing against the service.

OpenCodex context:

Suggested mapping or implementation notes

  1. Register AgentRouter as a first-class compatibility policy/preset.
  2. For parsed hostnames exactly equal to agentrouter.org or ending in .agentrouter.org, provide default static headers only when absent:
{
  "User-Agent": "codex_cli_rs/<detected-current-version>",
  "originator": "codex_cli_rs"
}

If impersonating Codex is not acceptable, use a stable OpenCodex-owned fingerprint coordinated/documented with AgentRouter, or require an explicit compatibility option.

  1. Use existing case-insensitive mergeRegistryStaticHeaders() semantics so operator headers win.
  2. Share exact-host AgentRouter policy between anthropic and openai-chat adapters.
  3. Cover translated /v1/responses and raw Chat Completions passthrough.
  4. Preserve user content byte-for-byte, keep framing idempotent, and never match hostile lookalike hostnames such as agentrouter.org.attacker.example.
  5. Map unauthorized_client_error to an actionable diagnostic.

Additional context and attachments

The final local provider was validated across restart with:

  • current Codex fingerprint 0.149.1;
  • two consecutive Portuguese Codex requests returning 200;
  • exact expected output;
  • no patch in OpenCodex adapters;
  • original AgentRouter providers disabled during isolated testing;
  • custom provider created through the 2.33.0 management API.

The key conclusion is that #2162 fixed only Anthropic first-turn framing. It did not make AgentRouter openai-chat providers self-configuring, and it did not address AgentRouter's required client identity.

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on a concrete Codex CLI client requirement and controlled upstream behavior.

Activity

  1. github-actions commented on Aug 27, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  2. rrmlima commented on Aug 27, 2026

    @rrmlima
    ContributorAuthor

    Updated the report to use the repository's recognized issue-template headings and filled every required field requested by the quality bot. The reproduction and all examples are redacted. Please re-evaluate/reopen automatically.

  3. rrmlima commented on Aug 27, 2026

    @rrmlima
    ContributorAuthor

    Follow-up: corrected headings to the exact GitHub issue-form rendering (). For #2717 I also added the literal Bug-template fields , , , , and because the quality bot classified the existing issue as kind=bug. All requested fields are now present.

  4. rrmlima commented on Aug 27, 2026

    @rrmlima
    ContributorAuthor

    Follow-up: corrected headings to the exact GitHub issue-form rendering (###). For #2717 I also added the literal Bug-template fields Summary, Reproduction, Version, Operating system, and Provider and model because the quality bot classified the existing issue as kind=bug. All requested fields are now present.

  5. lidge-jun commented on Aug 27, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 66 / 80

    설명: 이 이슈는 AgentRouter 를 openai-chat 어댑터로 쓸 때 생기는 두 갈래 버그다. 첫째, 인정된 Codex 클라이언트 지문이 없으면 업스트림이 401 unauthorized_client_error 를 낸다. 둘째, 지문을 넣어도 짧은 포르투갈어 요청은 400 content-blocked 가 난다. 작성자는 rrmlima 이다. 라벨은 bug, provider-compatibility, provider 다. 보고 버전은 OpenCodex 2.33.0 이다. 지금 CURRENT dev HEAD 는 ab63ded 이다. package.json 은 아직 2.32.1-preview.20260825 다. 이번 시간 origin/dev 는 움직이지 않았다. 마지막 머지는 2708 문서다. 이 이슈는 열려 있다. 구현 풀은 아직 없다. 내가 머지하지 않고 구현하지 않는다.

    HEAD 를 보면 본문 주장이 맞다. src/adapters/anthropic.ts 650-698줄에 AGENTROUTER_LANGUAGE_PREAMBLE 과 isAgentRouterEndpoint, applyAgentRouterLanguageFraming 이 있다. 호스트는 agentrouter.org 와 .agentrouter.org 끝만 본다. 938줄에서 anthropic 경로만 그 프레이밍을 켠다. src/adapters/openai-chat.ts 안에는 agentrouter, isAgentRouter, language framing, User-Agent, originator 문자열이 없다. 그래서 openai-chat 커스텀 프로바이더는 기본 지문도 없고 언어 프레이밍도 없다. 본문의 헤더 표와 맞다.

    무료 디렉터리 프리셋도 anthropic 쪽이다. src/providers/free-directory.ts 121줄 agentrouter 행은 adapter: anthropic 이다. 운영자가 직접 adapter openai-chat 과 baseUrl https://agentrouter.org/v1 을 만들면 그 프리셋을 타지 않는다. src/providers/registry.ts 에는 agentrouter 정적 행이 없다. mergeRegistryStaticHeaders 는 2844-2856줄에 있고, 사용자 헤더가 같은 이름을 치면 레지스트리 기본을 덮는다. 본문이 말한 운영자 헤더 우선 규칙은 이미 그 함수로 가능하다. 다만 AgentRouter openai-chat 기본 지문을 넣는 호출 자체가 없다.

    관련 이슈도 정리된다. 2074 는 비영어 원보고다. 2162 는 anthropic 쪽 언어 프레이밍을 합쳤다. 2415 는 openai-chat 에도 같은 프레이밍을 넣자는 이슈였는데 closed 이고 not planned 쪽이다. 이번 이슈는 2415 의 언어 구멍에다가 클라이언트 지문 401 을 새로 붙였다. 2415 를 다시 열지 말고, 이 이슈 하나로 지문과 언어를 같이 다루는 편이 낫다. types.ts 와 config.ts 가르기와는 무관하다. 프리뷰 배포가 아니다.

    라인 650-698 - src/adapters/anthropic.ts 에만 AgentRouter 언어 프레이밍이 있다
    라인 938 - anthropic 요청 경로에서만 isAgentRouterEndpoint 일 때 프레이밍을 켠다
    경로/심볼 - src/adapters/openai-chat.ts 에 AgentRouter 호스트 검사와 지문 기본값이 없다
    라인 121 - src/providers/free-directory.ts agentrouter 프리셋은 adapter anthropic 이다. openai-chat 커스텀은 이 행을 안 탄다
    경로/심볼 - src/providers/registry.ts 에 agentrouter 정적 행과 staticHeaders 가 없다
    라인 2844-2856 - mergeRegistryStaticHeaders 는 사용자 헤더 우선이다. 기본 지문을 넣어도 운영자 덮어쓰기는 가능하다
    이슈 2415 - openai-chat 언어 프레이밍 요청이 이미 닫혀 있다. 이 이슈로 대체해 다루는 편이 맞다
    이슈 2162 - anthropic 만 고쳤다. openai-chat 구멍은 남았다

    메인테이너의 판단이 필요한 지점

    • Codex 를 흉내 내는 User-Agent 와 originator 기본값을 OpenCodex 가 넣어도 되는지
    • OpenCodex 자체 지문을 AgentRouter 와 맞춰 문서화할지, 아니면 호스트별 호환 옵션만 둘지
    • 언어 프레이밍을 anthropic.ts 에서 공유 모듈로 빼서 openai-chat 에도 쓸지
    • 2415 를 다시 열지. 열지 말 것. 이 이슈로 묶는다
    • unauthorized_client_error 를 진단 메시지로 바꿀지. 바꾸는 편이 맞다

    너의 추천
    이 이슈는 연다. 닫지 말 것. 구현 풀을 새로 받는다. 정확한 agentrouter.org 호스트에만, 없을 때만 User-Agent 와 originator 기본값을 넣고, anthropic 과 같은 언어 프레이밍을 openai-chat 에도 공유한다. 운영자 headers 는 그대로 이긴다. 적대적 호스트 부분 문자열 매칭은 금지한다. 2415 는 다시 열지 말고 이 이슈에 링크만 남긴다. 라벨은 그대로 둔다. 프리뷰 배포가 아니다. 내가 머지하지 않는다.

    이 댓글은 grok-bot이 작성했습니다

  6. rrmlima commented on Aug 28, 2026

    @rrmlima
    ContributorAuthor

    I implemented and validated the focused contribution proposed by the review on clean 2.34.0 main.

    Controlled admission matrix, same valid key/model/body:

    Provider headers Result
    none 401 unauthorized_client_error
    version: 0.149.1 only 401
    User-Agent: codex_cli_rs/0.149.1 only 200
    originator: codex_cli_rs only 200
    UA + originator + current version 200
    UA + originator + old version 200

    This proves no stale Codex version needs to be pinned. The smallest stable default is originator: codex_cli_rs.

    The prepared change:

    • extracts the existing AgentRouter hostname/framing policy into one shared adapter helper;
    • preserves current parsed-host matching and rejects lookalikes (notagentrouter.example, agentrouter.org.attacker.example);
    • adds originator: codex_cli_rs only to AgentRouter openai-chat routes;
    • lets operator-configured originator win case-insensitively (originator or Originator), avoiding duplicate equivalent headers;
    • applies the existing first-user language framing to both translated Responses→Chat and native Chat passthrough;
    • uses a structured clone so the original request object and user text are not mutated;
    • keeps original user text as a separate content block.

    Fingerprint admission and language framing remain separate concerns: a direct minimal Portuguese request can pass 401 admission and still receive 400 content-blocked; the shared framing addresses that second stage.

    Validation:

    166 pass, 0 fail
    bun x tsc --noEmit: pass
    

    The suite includes Anthropic framing regression coverage, OpenAI Chat translated/passthrough paths, header precedence, lookalike hosts, error fidelity, and the full Chat Completions endpoint suite. The implementation is committed locally and ready for a standalone PR. No keys, request IDs, or account data are included.

  7. lidge-jun commented on Aug 28, 2026

    @lidge-jun
    Owner

    Fixed on dev by commit bb3321c (PR #2843), reimplementing @rrmlima's #2796.

    The openai-chat adapter path now sends the Codex client identity and request framing, so AgentRouter providers reached over that path get the fingerprint — previously only the anthropic path did, which is exactly what this issue reported. The host/framing policy now lives in one shared module so the two adapters cannot diverge again.

    Verified with tests/openai-chat-hardening.test.ts driven RED (61 pass / 3 fail) then GREEN (64 pass / 0 fail), plus an independent security audit of host canonicalization and lookalike-host rejection.

    Note this PR targeted dev, so GitHub did not auto-close this issue — closing manually.

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