Why this exists
Two goals, one mechanism. Make Claude Code reachable for students and researchers on the model plans they already hold. And raise the quality of work done inside Claude Code by letting a model that did not write the work be the one that checks it.
Claude Code is an agent harness, not a chat window: tools, subagents, skills, MCP servers and permission
prompts in one loop. Learning it is worth a student's time; paying for it is often not possible. Many
students and researchers already hold a plan of some kind, Google's student Gemini offer, a ChatGPT plan, an
xAI plan. claude-oauth lets those plans answer inside Claude Code over the account's own OAuth
session, with no API key and no separate bill. The native claude path stays exactly as installed
for the day you move to it.
The second goal is the one that shaped the code. A model that wrote a change is the worst judge of that
change. The working pattern here is a maker and a checker that are never the same model: one produces the
diff, the manuscript, the analysis; another, in a fresh context, reads the output and reports what is wrong
with it. Claude Code already has the mechanism, the Agent tool and subagents. What it lacks is a
second model to hand the work to. This repository supplies it: one delegate agent per routed model, and a
/model switch that puts a Google, xAI or OpenAI model in the driver's seat for a review pass
while your tools, permissions and MCP servers stay where they are.
- Built the way it asks to be used. The router was written and reviewed by different models in turn; the defect list further down is what that loop produced. The Google native-write bypass (B01) was found by an OpenAI model after the author's own measurement had passed it. The N05 repair was written by an OpenAI model in a separate worktree and accepted by a gate the reviewing side ran, not the model that wrote it.
- The checker's answer is evidence, not truth. Run the test it points at, open the file it names. Where the two models disagree, the disagreement is the finding.
- What limits this today is written down. Generated delegate agents answer from what the prompt hands
them and cannot drive the harness themselves (N03). A direct
/modelswitch to the Google or xAI lane carries the tool loop (G01, G03), so a review run that way can open files and run tests itself. - Not a way around Anthropic. The native path is untouched and remains the recommended one. Someone who learns Claude Code on a free tier and later wants the native models has a reason to pay for them; the intent is adoption of the harness, not a route around it.
The method behind the maker-and-checker habit has its own project, autofusion: freeze the artifact, send it to reviewers that cannot see each other, ground checkable findings with the verification commands the repository already trusts, and keep unresolved disagreement visible in a metadata-only receipt. autofusion provides the method; this router provides the plumbing, the models reachable through the sessions you already hold. If you work intensely with AI, the recommendation from both projects is the same: keep one model for routine work, and run fusion on the decisions that matter, where a missed issue is costly or hard to reverse.
Two commands, one boundary
claude is the Claude Code you already have, and this project never touches it.
claude-oauth starts that same Claude Code with a loopback router behind it, so Google,
xAI and OpenAI models answer inside the harness, over your own accounts, on your own plans.
The command you run decides the path. There is no mode to remember and nothing to switch back.
claudeapi.anthropic.com
No proxy, no patch, no interception. The binary, its configuration and its traffic are left as they were.
claude-oauthagyyour Google OAuthgrokyour xAI OAuthWhere Claude Code sits
claude-oauth is not a script that calls three CLIs. It launches a real Claude Code process, the
binary you installed, at the version pinned in config/install-lock.json, and sets that process's
upstream endpoint to a router on the loopback interface. Claude Code keeps everything that makes it a harness:
its tools, its permission prompts, its skills, your MCP configuration. Only the model behind the
/v1/messages call changes.
When the selected model is a Claude model, the router forwards the request to Anthropic unchanged, with the
OAuth token Claude Code itself attached. When it is one of the three lanes, the router drives that lane and
translates its answer back into the message shape Claude Code expects. The router runs only for the
claude-oauth session that started it.
Changing model, including mid-session
At launch
claude-oauth --model grok, or gemini, gemini-pro,
opus-google, sol, terra, astra. Any other argument is
passed to Claude Code as-is.
Mid-session
Claude Code's own /model command. Routed models appear in that picker once
claude-oauth models refresh has written them into the model snapshot; until then the picker
shows only Claude models. Switching back to a Claude model is the same command.
As a subagent
Inside a claude-oauth session the launcher registers one delegate per routed model
(grok-delege, gemini-delege, sol-delege, …), so a Claude model can
hand a task to another model through the Agent tool. Today those delegates answer; they do not
drive the harness; see N03 below.
What each lane actually runs
Each lane authenticates as you, on your own plan. Two of the three run the vendor's own CLI. The third does not, and an earlier draft of this page said otherwise. That was wrong and is corrected here.
| lane | what runs | whose software | your session |
|---|---|---|---|
Antigravity CLI agy, headless, one process per call | Google OAuth | ||
| xAI | grok over ACP (Agent Client Protocol), long-lived sessions, at most four live at once | xAI | xAI OAuth |
| OpenAI | Clodex, a third-party bridge (@bman654/clodex 2.11.1, MIT), run as a local capsule with its openai-oauth provider and local patches from config/ | bman654 (not OpenAI) | ChatGPT / Codex-plan OAuth |
Versions and digests of all three are pinned in config/install-lock.json. Nothing here grants an
entitlement: if your plan does not include a model, the router cannot conjure it, and quota exhaustion surfaces
as quota exhaustion.
The claim, and its measurement
"Installing this does not degrade native Claude Code" is a claim, so it gets a measurement instead of a paragraph. Measured on the maintainer's machine, 2026-09-07, and reproduced by the shipped probe on 2026-09-08:
| scenario | check | result |
|---|---|---|
| Environment | ANTHROPIC_BASE_URL in a normal claude session | absent |
| Process tree | ancestry of the claude process | no router process |
| Positive control | claude remote-control --help in a clean environment | accepted, full help |
| Negative control | the same command with a router-style base URL set | refused, exit 1 |
The last row is the point. A check that cannot fail proves nothing; the passing rows mean something only because the negative control reliably fails.
Scope, stated plainly. One machine. The negative control used a synthetic base URL, not a live router
window. Since 2026-09-08 the repository ships the launcher shims and a two-arm probe
(node scripts/native-separation-probe.mjs) that reproduces both controls from a clone; on this
machine both arms pass, and the README records the exact output. B05 is closed. What the probe does not
establish is process ancestry or a live router session's behaviour; those stay maintainer measurements.
The target, and the distance to it
Claude Code is an agent harness, not a chat window: tools, subagents, skills, MCP servers, permission prompts,
file edits you approve. The target for this router is that every model reachable through it can use that same
surface: the Agent tool, skills, MCP servers such as Playwright, file editing under the same
permission rules a native session has.
As of 2026-09-08 the Google lane reaches that surface for real: the router logged the provider agent parking a
tool call, Claude Code executed it 213 ms later, and the model returned a string it had no other way to know
(G01, repaired). The native permission paths in Google and xAI are constrained (B01), and permission
inheritance through a separate Agent child session was measured live the same day: the child
takes the parent's permission set, and the routed parent reached Claude Code's Agent tool
through the bridge (B01 row below).
Defects and repair status
Found by review, by independent passes on 2026-09-07 and 2026-09-08, and by an operator report on 2026-09-10, mostly by models that had not written
the code, then measured one by one. Ids are the repository's own and are stable across the README,
SECURITY.md and CHANGELOG.md. As of 2026-09-10 every row carries a measured repair;
where a repair has a residual scope, the row says exactly what it does not establish. This table is generated
from the README's Known gaps table so the two cannot drift apart.
| id | status | what was wrong, what changed, how it is measured |
|---|---|---|
| B01 | fixed | Repaired and measured 2026-09-08, Agent arm included. Google direct route: call-scoped settings allow only the bridge MCP server and deny native write_file, command, browser, execute_url and unsandboxed actions; a live Claude Code Write denial leaves no file and a native write_to_file attempt is denied too. xAI rejects native ACP writes and admits only identified bridge MCP permission requests. Agent child sessions, measured live on the Google lane: the routed parent reached Claude Code's Agent tool through the bridge (router park events Agent, ListAgents); with Write disallowed on the parent the child could not create the file (positive arm, no file), with Write allowed the child wrote it (negative arm, file present with the exact content). Inheritance is Claude Code's own mechanism: the child takes the parent's permission set, and every router call to the Google lane gets its own call-scoped denial settings regardless of which session made it. Claude Code chooses the child model itself (haiku in one arm, the routed Gemini in the other); the router does not set it. Receipt: the streams and router.log lines are in the maintainer's measurement notes. |
| B02 | fixed | Repaired 2026-09-07 (b5caf0e). A role:"system" message arriving after the last user message was dropped silently while compiling the request, measured loss ~77.5k characters: the skill catalogue, the agent catalogue, MCP server instructions, the output style. It now travels in its own SESSION CAPABILITY CONTEXT section. Six test arms, mutation-verified. The end-to-end claim is NOT made here: G01 still keeps the Google lane off the tool surface. |
| B03 | fixed | Repaired 2026-09-08. Compilation and result extraction share the last logical conversation record, so trailing system records retain the live session. Every continuation is validated and its system context plus all accompanying user text travels in labelled MCP result text before the agent resumes. Tests measure one start, the instruction received, mixed images, trailing blank text, and a usage bound covering the full request body. This preserves transport, not a guarantee of model compliance. |
| B04 | fixed residual scope |
Repaired 2026-09-08 for the checked-file replacement race. Writes use a non-truncating open, validate the handle identity, and write through that handle; new files use exclusive creation, with deterministic Windows symlink/junction tests proving outside target contents stay unchanged. Arbitrary ancestor replacement remains a limitation requiring platform-specific filesystem support; see SECURITY.md. |
| B05 | fixed | Repaired 2026-09-08. Portable PowerShell/POSIX shims and a reproducible two-arm probe ship in this tree; stub tests measure native environment cleanup and router entry selection. Live probe: both arms PASS on Windows, 2026-09-08, output recorded above. |
| B06 | fixed | Repaired 2026-09-08. max_input_tokens uses the lower of the advertised window and snapshot transport capacity, tested for every routed model against its pin; Astra's context_window remains a presentation value of 1,000,000 while input is capped at the unchanged 872,000 pin: accepted capacity unproven, advertisement capped at the pin. |
| B07 | fixed | Found and repaired 2026-09-08 (Agent arm). When the Google model chose one of its own native tools (read_file) instead of the bridged Claude Code tools, the call-scoped settings denied it, agy ended without an answer, and the bridge returned the denial as HTTP 502; Claude Code treated 502 as transient and retried ten times with exponential backoff, every retry meeting the same policy. The denial now travels as 403 (terminal for the turn, not retried) and the bridged prompt carries one policy sentence telling the model that its native file, shell and browser tools are disabled here and naming the MCP server to use. Tests: test/headless-bridge-denial-status.test.ts, test/headless-bridge-policy-note.test.ts; mutation control over the whole suite fails one test per reverted change. Whether the model now picks the bridged tools more often is a model behaviour, not something this repair proves. |
| B08 | fixed | Found and repaired 2026-09-08 (two-step bridged tool probe on the Google lane). The router reported request bytes as input_tokens; Claude Code read a 320 kB second turn as 320k tokens against the 200k window it assumes for a model id without the [1m] suffix, started an automatic compaction, and the compaction request replayed the just-delivered tool_result, which the registry refused as "no live agent session" (409) ten times with backoff until the turn failed. Repair: the registry remembers delivered ids (bounded, past retirement) and the adapter runs an all-delivered result set as a fresh turn, logging agent_tool_result_replayed; the reported input bound is now ceil(bytes / 3), an estimate and labelled as one; every session retirement logs its reason (agent_session_retired). Test: test/mcp-replayed-result.test.ts; mutation control over the whole suite fails it and the usage-bound test. Pick the [1m] model id in the picker for the full Gemini window. |
| B09 | fixed | Found and repaired 2026-09-10 (operator report: claude-oauth refused to start). The OpenAI lane wrote its patched Clodex capsule to dist/clodex-capsules/session-* inside the pinned release, and dist is one of the two directories the content-addressed release id hashes. A session that ended without close() left the capsule behind, the release no longer hashed to its own id, and the next start refused it with release_contents_do_not_match_release_id. The refusal was correct: the detector fired on a real edit-in-place; the defect was where runtime state lived. Repair: capsules are written under <release>/.runtime/clodex-capsules/, still below the module root so bare ESM imports resolve against the release's own dependencies, and outside every hashed directory. Test: test/clodex-capsule-entrypoint.test.ts asserts the capsule path is under .runtime and never under dist or config; mutation control over the whole suite fails it. A fresh release was cut and pinned; the drifted release directory was left untouched as evidence. |
| B10 | fixed | Found and repaired 2026-09-10 (operator report: every GPT model showed a 200k window). Claude Code does not read the context_window that discovery advertises; it assumes 200k for any model id without the [1m] suffix (measured under B08). The router attached the suffix only when the SNAPSHOT window equalled the contract ceiling, so Astra's advertised 1,000,000 never reached the client and Astra ran at 200k with compaction far too early. Repair: the suffix follows the ADVERTISED window; Astra is anthropic-openai-gpt-6-astra[1m] and takes the 650k autoCompactWindow from settings, the same as Opus. Sol and Terra advertise their 872,000 snapshot and stay at the 200k assumption on purpose: it keeps them under the 272K input pricing boundary. Grok advertises 500,000 and also stays at 200k because Claude Code has only the two assumptions; a [1m] id would overflow at 500k before the 650k compaction. Test: test/contract-derivation.test.ts (positive arm Astra, negative arm Sol); mutation control over the whole suite fails it. |
| B11 | fixed | Found and repaired 2026-09-10 (operator report: Gemini answered with "Please run /login"). B07 returned a headless permission denial as 403; Claude Code reads 401 and 403 as an authentication failure and prompted for login on a lane whose OAuth session was fine. Repair: a denial is 400, terminal, shown as an API error, not retried, never mistaken for an expired login. Test: test/headless-bridge-denial-status.test.ts; mutation control over the whole suite fails it. The denial the operator saw was attributed to a native read_file outside the allow list of the call-scoped agy settings. That attribution was tested on 2026-09-13 against the repaired release and did not hold. Three probes ran real agy 1.1.27 in exactly the routed configuration, against a real MCP endpoint: a plain file read, a call to a tool named mcp__playwright__browser_navigate, and an instruction to avoid MCP and use a native file tool. All three succeeded through the MCP surface; the model never reached for a native tool. No allow-list entry was added, because none was shown to be needed. Whether a routed Gemini can drive Playwright end to end in a live session is still NOT_RUN. |
| B12 | fixed | Found and repaired 2026-09-10 (operator report: after /model to Grok, /compact and every turn failed). The Google and xAI lanes hold separate session registries. After a switch, the conversation still carried the previous lane's tool_result blocks; the new lane's registry had never minted those ids and refused the turn as "no live agent session" (409), 44 times in one session. Claude Code cannot strip tool_result blocks from its own history, so the refusal was a dead end. Repair: a result set the registry never parked is neither a continuation nor an orphan; it runs as a fresh turn over the compiled history and logs agent_tool_result_foreign. Only ids of a live session continue that session. Test: test/mcp-foreign-result.test.ts; mutation control over the whole suite fails it. |
| G01 | fixed | Repaired 2026-09-08. The Google lane now carries the same tool loop the xAI lane has. The objection that kept it away was that agy mcp add writes the session nonce into the operator's persistent config; the lane instead runs inside a call-scoped configuration home (USERPROFILE redirected, identity files hard-linked, never copied), so the operator's own mcp_config.json is never opened for writing. Live receipt: the router logged agent_tool_call_parked, Claude Code executed the MCP tool 213 ms later, one tool_use block crossed the stream, and the model returned a string it could not otherwise know. NOT claimed: a multi-step loop, or the Agent sub-loop. |
| G02 | fixed | Repaired 2026-09-08, diagnostic branch. Installed Grok 1.0.13 documentation (docs/user-guide/07-mcp-servers.md, Compatibility) says project .mcp.json is loaded unless the Claude import marker suppresses it. Refusal is retained and now names .mcp.json plus a workspace/user-configuration remedy; root and nested-workspace tests measure it, with hooks/plugins still fail-closed. Live xAI in repositories with non-empty .mcp.json: NOT_RUN, intentionally refused because Grok can discover this file. |
| G03 | fixed | Repaired 2026-09-08. A session parked on a tool call set running = false and so looked idle; at the cap the oldest such session was cancelled mid-loop and the returning tool_result found no session. A first repair used a 60 s age window; an independent wall-clock measurement then showed a legitimate 61 s permission wait being sacrificed, so age was dropped as the discriminator. Now a parked session is never evicted for capacity: at the cap the request receives an explicit 503. The cost is stated in the source: an abandoned parked session holds capacity until the idle timeout or the provider's own timeout. Both registries also close and wait on shutdown (previously only the xAI one did; four Google processes and homes were measured surviving close()). |
| G04 | fixed | Repaired 2026-09-08. Agent history drops thinking and redacted_thinking, summarizes image and document with media type and size, and renders server_tool_use and mcp_tool_use like tool calls. Per-type Google/xAI tests and a session-lane fixture cover messages and token counting without 422. Media history remains a placeholder; initial multi-text request framing and delegate tooling are outside this repair. |
| G08 | fixed | Repaired 2026-09-08. The SDK-typed HTTP definition and a fake ACP peer verify exact name, URL and {name,value}[] headers. Live Grok 1.0.13 reached the router bridge: agent_tool_call_parked at 2026-09-08T10:23:44.844Z, caller PID 49944, tool cluster_a_handshake_receipt; the reply completed with end_turn and zero unmatched results. Header removal is exercised as a full-suite mutation control. |
| N01 | fixed | Repaired 2026-09-08. Anthropic and native MCP base64 image tool results cross the shared bridge on both xAI/ACP and Google/Antigravity lanes; text compilation and unsupported/URL media use type, media-type and byte-size placeholders (URL size is unknown). Embedded resources retain known MIME type and payload size. Adapter-to-MCP tests preserve image bytes, MIME casing and error status; the production-log fixture measures payload-free image metadata. Live provider image interpretation is NOT_RUN, no vision task was submitted. |
| N03 | fixed | Repaired 2026-09-08. Google/xAI inherit tools without a launcher turn cap; OpenAI defaults include Skill and user definitions may add MCP names. Generated JSON and wider/narrower overrides are tested in both client modes; changed or removed model identities still receive 400. Live Agent tool loops remain NOT_RUN, requiring an authenticated child-session harness. |
| N04 | fixed | Repaired 2026-09-08. Every request derives its catalogue and replaces changed bridge descriptors before releasing parked results; the xAI permission lookup reads the live names. Tests cover ToolSearch adding mcp__playwright__browser_snapshot, schema changes, removal, and unchanged descriptors. Already parked removed-tool calls can finish; subsequent calls are refused. Provider-side catalogue cache refresh is NOT_RUN, the offline measurement covers tools/list. |
| N05 | fixed | Repaired 2026-09-08. The capsule runs a hash-pinned patched copy of Clodex and receives its session nonce only through the child environment. Measured with an anonymous scratch home, missing/wrong/correct x-api-key: catalog and health 401/401/200, malformed JSON messages 401/401/400. Empty nonce, missing patch anchors and patched hash drift prevent startup. |
| N06 | fixed | Repaired 2026-09-08. Captured helpers receive SIGTERM, then SIGKILL after configurable terminationGraceMs (default 100 ms), and reject with a timeout even without exit; Windows tests cover the deadline and missing-exit case, while the meaningful SIGTERM-resistant Linux arm is NOT_RUN locally (Windows host). |
| P01 | fixed | Repaired 2026-09-08. Found while measuring N05 on Linux: the install lock is written with Windows variables (%USERPROFILE%, %LOCALAPPDATA%, %APPDATA%), and on POSIX doctor died in path expansion before reporting a single component. On POSIX those variables now resolve to HOME, XDG_DATA_HOME (else ~/.local/share) and XDG_CONFIG_HOME (else ~/.config), backslashes become separators, and a binary that is not there is reported as missing instead of aborting the diagnosis. Measured: WSL2 doctor lists components; unit arms in test/install-lock-posix-paths.test.ts, mutation control fails 3 tests. Linux binaries are still not pinned by the lock, so on Linux doctor reports them missing until a POSIX lock exists. |
How it is built
TypeScript on Node 24. Two pinned runtime dependencies, @agentclientprotocol/sdk for the ACP lanes
and @bman654/clodex for the OpenAI capsule, and nothing else at run time. A check that has to stay
green on Linux and Windows: lint, type-check, build, tests, secret scan.
- Every gate carries its negative control.
lint,secret-scanandrelease-ideach have an--ozdenetimarm that injects a synthetic defect and proves the gate catches it. A green run whose instrument has never fired is a green name, not a green measurement. - Diagnostics that survive. When a lane fails, the child process's own explanation is carried into the error: bounded in bytes, redacted before it travels, cut on a code-point boundary.
- Named causes only. An error code states a cause that was observed. Where the evidence is missing, the generic code stays; a confident guess sends the reader to the wrong fix.
- Test counts live in CI, not on this page. A number on a page rots the day after it is written.
# from a clone npm ci # lint + typecheck + build + tests + secret scan npm run check # each gate shown catching its own canary npm run self-check
What a clone gives you: the build, every test, the gates and their negative controls, the request compiler and
the lane bridges to read, and offline probes behind several findings. What it does not give you yet: an
installation. There is no shim (B05), no documented release builder, and the install lock pins binaries at
Windows locations. node dist/src/cli.js doctor will list, component by component, what it cannot
find. That is the intended behaviour.
Anthropic's path, untouched
This project does not proxy, wrap or intercept Anthropic's endpoint, does not share or resell access, and does
not change what any account is entitled to. The native claude command is left exactly as
installed and remains the recommended way to use Claude Code. The measurement above is how that sentence is
checked; B05 is what still stands between the sentence and a reader's ability to check it themselves.
The intent is the one stated at the top of this page: a student or researcher who can reach a provider plan but not a paid Claude plan learns the harness inside Claude Code rather than outside it, and a developer gets a second model to check the first one's work. Both spend their hours in Claude Code. Whether that serves anyone beyond them is not a claim this page makes.