Repository navigation
Spike Claude Code Messages compatibility through CodexHub #74
Description
Part of #73.
Blocked by: nothing (native dependencies). This is the first gate of the campaign.
Status: needs-info. The remaining acceptance criteria cannot be completed without approved live upstream access and explicit authorization to use it. Historical scoped-PARTIAL evidence exists (PR #100 at 0aa1f3d3449264437c3c9efe097417bc31c3443c) but it establishes message shapes, not three-protocol readiness. It is historical and must not be cited as a GO.
Objective
Re-establish the exact Claude Code → CodexHub wire contract with a pinned real Claude Code CLI against approved real upstream access, for all three upstream protocols, and record a GO / scoped-PARTIAL / NO-GO decision for production /v1/messages support.
This is an evidence gate. Do not add production routing, client configuration, or client UI in this issue.
Authority and prior decisions
- ADR-0014 replaces ADR-0001's blanket rejection of non-equivalent options with declared best-effort adaptation. This Spike must supply the evidence that policy is safe, field by field.
- ADR-0001's representation seam, ordered content, opaque-field preservation, credential separation, and declared-adaptation outcomes still stand.
- Product decisions (three protocols, full model picker, critical-content safety) are canonical in Track Claude Code as an external CodexHub Gateway client #73.
Scope
1. Pin the client
- Install and record the exact Claude Code executable version, platform, and runtime. Historical public-source evidence used v2.1.207; prior local evidence used 2.1.201. This campaign currently observes installed 2.1.278; test that exact binary without treating historical pins as a supported-version floor. Record the version actually used, and never present one version's result as another's.
- Record the startup traffic actually observed:
POST /v1/messages(and?beta=truepath-vs-query matching), optionalPOST /v1/messages/count_tokens, any best-effortHEAD/connectivity probe, and model discovery.
2. Model roles and discovery (facts to establish, not assume)
- Which environment variables / config fields the pinned CLI actually honors for: main model, fast/background model, subagent model, and any single custom-model option.
- Whether discovery runs by default, its exact request (
GET /v1/models?limit=1000, three-second timeout, redirect and auth behavior), and the exact response shape it requires: envelope fields, paging fields, display metadata, and required ID form. - Whether the pinned CLI enforces a model-ID prefix rule (e.g.
claude*/anthropic*) on discovery results or accepts arbitrary IDs. This decides how Expose every Gateway model to Claude Code with fixed-name and role mapping #77 can make every exported model selectable. - Refresh behavior: what makes Claude Code pick up a changed model list (restart, cache, TTL).
- Whether an explicit in-session model selection is overridden by any role mapping or default, and what the CLI reports as the effective model.
- Exact role/alias names the CLI sends or hardcodes, captured as observed strings.
3. Credential carrier
- Observe which carrier the pinned CLI sends under a documented configuration:
Authorization: BearerfromANTHROPIC_AUTH_TOKEN,x-api-keyfromANTHROPIC_API_KEY, both, orapiKeyHelper. - Recommend what the Gateway must accept, without weakening Harden local Gateway auth so codex-app context is not spoofable #24, and state the fail-closed default when the carrier is wrong.
4. Capability classification across all three upstream protocols
Exercise the isolated translator prototype against real, approved upstreams of each format — native Anthropic Messages, OpenAI Responses, and Chat Completions — through CodexHub. For each format, classify: text, incremental SSE, multi-turn history, one tool call + tool_result follow-up, parallel tools, images, thinking/effort, anthropic-beta/body pairs, prompt caching, compaction/resume, subagents, count_tokens, usage, cancellation, and error/retry behavior as preserved, adapted (named policy) or explicitly unsupported, each with sanitized evidence.
5. Native-path fidelity
Prove what the Anthropic upstream path preserves end to end — semantic headers, ordered content blocks, opaque/unmodelled fields, JSON and SSE event order — and exactly what the Gateway rewrites (model identity, credentials, transport safety). The native path must not lose a field merely because a cross-protocol adapter cannot represent it.
6. Live access is a prerequisite, not an inference
- Live evidence requires upstream access that is explicitly approved for this use. A provider being configured in the user's catalog is not consent, and no credential material belongs in this issue or its fixtures.
- If approval is not granted, record the concrete blocker and leave this issue in
needs-info; do not substitute mock-only results for the live criteria.
7. Downstream deliverables
- Re-establish the pure
AnthropicMessagerepresentation seam required by ADR-0001/ADR-0014, reusing existing logic where compatible; do not reopen the accepted seam implicitly. - Record each non-equivalent field's named adaptation policy and the diagnostic text it produces (feeds Add inbound Anthropic Messages API for Claude Code clients #75 and the client card's compatibility state).
- Update
docs/reviews/v0.3-agent-provider-roadmap.mdto keep downstream client support distinct from upstreamAgentProviderwork.
Out of scope
Production /v1/messages registration, codex_proxy.py handler integration, client configuration or launcher, discovery UI, aliases, and any AgentProvider/ACP work.
Acceptance criteria
- A pinned real Claude Code CLI version and platform are recorded, and every claim names the version it was observed on.
- Endpoint, query-variant, probe, discovery, and role mechanisms are documented as an open-set contract from real observation.
- Role names/variables, discovery response shape, ID-prefix behavior, refresh behavior, and effective-model reporting are recorded as observed facts.
- The credential carrier actually used is recorded, with a recommended Gateway acceptance rule that does not weaken Harden local Gateway auth so codex-app context is not spoofable #24.
- A real "read file → tool call → tool result → final answer" lifecycle succeeds with stable call identity on each approved upstream protocol.
- Incremental SSE (not buffered) is observed on each approved upstream protocol.
- Sanitized traces cover request, JSON/SSE response, error, cancellation, and usage shapes for all three protocols, with no credentials or user content.
-
anthropic-version,anthropic-beta, andx-claude-code-*handling policies are documented as open-set contracts. - Every non-equivalent field has an explicit named adaptation policy or a fail-closed outcome; nothing is silently dropped.
- Native Anthropic fidelity (semantic fields + SSE) is proven separately from the converted paths.
- The report records GO, scoped PARTIAL, or NO-GO for production
/v1/messages. - The ADR-0001/ADR-0014 representation contract is verified and recorded.
- No production route or client configuration ships in this change.
Required verification
Risk class: strict (docs/agents/verification-policy.md) — protocol, transport, and auth boundary. Use the repository launcher; never a bare python/pytest.
Targeted checks during the Spike, then the relevant Python full suite once at the candidate commit:
.\scripts\codexhub-python.cmd -m pytest tests/test_route_plan_seam.py tests/test_chat_completions_gateway.py -q
.\scripts\codexhub-python.cmd -m pytest -q --ignore=tests/test_real_client_e2e.py
.\scripts\codexhub-python.cmd scripts/report_quality_gates.pyLinux: ./scripts/codexhub-python.sh ... with the same arguments. Real-CLI evidence is a separate local procedure; record exactly which environment produced it.
References
- https://code.claude.com/docs/en/llm-gateway-protocol
- https://code.claude.com/docs/en/model-config
docs/research/2026-07-12-claude-code-external-client-support.md- ADR-0014
Document status
ADR-0014 is committed in draft PR #556 at ed76f0a6e22d3188879504638b2943f5bb855d41, not yet merged to main. The pinned link is readable now; the document is accepted direction, not live protocol evidence.
Activity
NOirBRight commentedon Jul 12, 2026
Dispatch
NOirBRight commentedon Jul 12, 2026
Plan for #74:
Collision proof: production
src-python/codex_proxy.py, route registration,/v1/messages, and client auto-configuration are out of scope; the prototype will live in a new isolated module imported only by its focused tests.NOirBRight commentedon Jul 12, 2026
Scoped-PARTIAL evidence merged in PR #100 at 0aa1f3d.
Established: pinned local Claude CLI loopback, sanitized text/tool/history/SSE/error/usage evidence, isolated Messages translator/ADR, and fail-closed Chat-stream shape/identity controls. No production route or client configuration shipped.
Issue #74 remains open. A fresh autonomous worker cannot complete the remaining acceptance gates without approved real-provider access and authorization: official Responses plus one Chat Completions Provider proof, and the documented thinking/cache/image/beta/count-tokens/cancellation/retry compatibility gates. The Issue is therefore moved to needs-info and unassigned; #75-#78 remain blocked.
NOirBRight commentedon Sep 20, 2026
Campaign dispatch: draft PR #556, integration branch
campaign/claude-code-gateway, decision baseline3a9154c.Assigned implementer works in isolated
campaign/claude-ticket-74. Current authorization allows deterministic tests and isolated synthetic loopback only; real upstream resources/limits are requested from the user and remain pending. No production routing or client UI is authorized by this dispatch.Concurrent read-only preparation covers #77 discovery/mapping, #78 verification inputs, and overlap with open Clients PRs. Partial evidence will not close this ticket or unblock production descendants. Exact commits/checks and remaining live gates will be recorded before integration.
NOirBRight commentedon Sep 20, 2026
Offline checkpoint submitted for independent merger review: campaign/claude-ticket-74 @ edd9efd. Implementer reports focused 214 passed / 41 subtests, full Python 3039 passed / 180 skipped, and credential-free Claude 2.1.278 loopback evidence. This is PARTIAL only; no production route or client UI.
Authorization update: user approved a single Linux isolated round with <=20 actual upstream HTTP attempts per protocol (all retries/auxiliary calls included), <=2048 output tokens/request, <=30 minutes total, no automatic renewal. Responses target requested: Codex / Luna / max. Chat and Anthropic targets requested: CommandCode, but inspect candidates and discuss before choosing/calling. No dedicated credential input paths supplied yet. Therefore no real model call has been authorized to start; exact remaining targets and isolated inputs are still pending.
Independent model-selection and configuration investigations completed; the discovery substring filter now has actual synthetic-loopback confirmation. The prototype refuses safeguards whose semantics remain unknown, rather than silently weakening them. #75–#78 remain blocked.
spike: re-establish Claude Code Messages seam with loopback evidence
test: register the #74 loopback harness as a direct Python entrypoint
NOirBRight commentedon Sep 20, 2026
Offline response/transport checkpoint c8ba0c6 independently reviewed on both axes, merged as d57f486, and published with the reviewed input/admission correction in fbadd78. Combined core:3101 passed/180 skipped/267 subtests; Issue66 matrix reconciled; all three buffered loopback exchange/admission legs pass. Native bytes, truthful usage/zero counts, real source identities, explicit empty Chat text, malformed-success refusal, sanitized converted errors and cancellation guards have scoped regression coverage. This remains PARTIAL: no production /v1/messages route, no true-incremental/live GO. The next isolated #74 slice is native incremental transport with first-text-before-terminal, blocked-read cancellation and hard-deadline checks; converted incremental work remains later. No real upstream calls and no dependent ticket unblocked.
NOirBRight commentedon Sep 20, 2026
Additional bounded offline observation on fbadd78: pinned Claude Code 2.1.278 (SHA256 5c4735937844e84f8a93306e841a5b0e12252909b07870f789b190468da147ab) completed two text-only CLI+codec runs, one Responses and one Chat. Each first request was refused for safeguards; after the synthetic 400 named that unsupported field, the CLI itself retried without it. No Gateway code stripped it. The second request prepared successfully, a pure deterministic fixture was adapted to 200 SSE, and the CLI exited 0. Two admitted local requests/run, fresh isolated HOME and fake auth, 60-second cap/run; each observed run took 1.30s. Post-run strace found only loopback connections; this is detection, not OS containment. No upstream HTTP/model/real credential was used. This supports codec feasibility, NOT safeguards safety-equivalence, tool support, live transport, true incremental delivery, or production GO. Raw prompt/CLI/HOME records were erased; these are research observations, not the final acceptance artifact.
NOirBRight commentedon Sep 21, 2026
Native-only incremental relay is now in the campaign draft at 34cf9de (reviewed candidate ec4322b). Offline checks: first text_delta before terminal, real loopback cancellation/deadline via socket abort, complete event preserved before a later oversized frame, timeout bounds stalled polls, commit.cancel on EOF/non-success. Combined core: 3111 passed / 180 skipped / 267 subtests. Converted incremental remains explicitly unsupported. No production /v1/messages route, no live upstream, no GO. Next isolated slice is Responses/Chat incremental conversion; live three-protocol evidence still needs a fresh bounded grant and ready native input.
NOirBRight commentedon Sep 21, 2026
Converted incremental Responses/Chat relay is now in the campaign draft at 30be460 (reviewed d19cc73). Offline: text_delta before terminal for both converted formats; loopback cancel/deadline; Chat annotations fail closed; Responses streams without real id/model cannot emit fabricated chatcmpl_ UUIDs. Native byte passthrough unchanged. Combined core: 3117 passed / 180 skipped / 267 subtests. Still PARTIAL: no production /v1/messages, no live three-protocol evidence, no GO. Next remaining #74 gate is authorized live evidence with ready native input and a fresh bounded grant.