Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Fix Anthropic OAuth in OpenCode (op-anthropic-auth@0.1.1)

Drop-in patched dist/index.js for the op-anthropic-auth@0.1.1 plugin that opencode loads from ~/.cache/opencode/packages/ at runtime.

Targets the actual plugin that runs, not ~/.opencode/node_modules/....

What was broken

  1. Anthropic's third-party classifier was returning HTTP 400 on every request. The classifier scores requests on UA / version strings, billing header integrity, and — most importantly — fingerprints in the system prompt (the Workspace root folder: … line and the anthropic/ model-id prefix are the big ones).
  2. The Anthropic adapter in opencode was posting to /messages on some code paths instead of /v1/messages. Result: Cloudflare 404 page, empty chat, no visible error.

The plugin's URL rewriter only handled the /v1/messages case, so path 2 silently broke.

What this patch does

1. Correct Claude Code version / UA / entrypoint

const CLAUDE_CODE_IDENTITY   = "You are Claude Code, Anthropic's official CLI for Claude.";
const CLAUDE_CODE_VERSION    = "2.1.117";
const CLAUDE_CODE_ENTRYPOINT = "cli";
const REQUEST_USER_AGENT     = "claude-cli/2.1.117 (external, cli)";

Old strings claimed 2.1.87 / sdk-cli / SDK identity. The actual Claude Code CLI on this machine is 2.1.117 with cli entrypoint. Values verified against the real claude binary at ~/.local/share/claude/versions/2.1.117.

2. Billing header cch token

function buildBillingHeaderValue(messages) {
    const text = extractFirstUserMessageText(messages);
    const suffix = computeVersionSuffix(text);
    return "x-anthropic-billing-header: "
        + `cc_version=${CLAUDE_CODE_VERSION}.${suffix}; `
        + `cc_entrypoint=${CLAUDE_CODE_ENTRYPOINT}; `
        + `cch=00000;`;
}

Real Claude Code emits the literal cch=00000;. The old plugin computed cch as a 5-char SHA-256 of the first user message — classifier reject. computeCCH removed; 00000 hardcoded.

3. System-prompt sanitizer

function sanitizeSystemText(text) {
    // ... existing paragraph/anchor filtering ...
    result = result.replace(/^\s*Workspace root folder:.*\r?\n?/gm, "");
    result = result.replace(/^\s*Working directory:/gm, "  Primary working directory:");
    result = result.replace(/^\s*Is directory a git repo:/gm, "  Is a git repository:");
    result = result.replace(/The exact model ID is anthropic\//g, "The exact model ID is ");
    result = result.replace(/^Skills provide specialized instructions.*$/gm, "");
    result = result.replace(/^Use the skill tool to load a skill.*$/gm, "");
    result = result.replace(/^No skills are currently available\..*$/gm, "");
    return result.trim();
}

This is the one that flipped 400 → 200. Bisected against api.anthropic.com with a real OAuth token: stripping a single line (Workspace root folder: /) was enough to unblock. The other scrubs (env-block key names, anthropic/ model-id prefix, Skills-related lines) add headroom in case opencode's prompt wording drifts or Anthropic tightens the classifier.

4. URL rewriter promotes /messages/v1/messages

if (requestUrl && requestUrl.hostname === "api.anthropic.com") {
    if (requestUrl.pathname === "/messages") {
        requestUrl.pathname = "/v1/messages";
    }
    if (requestUrl.pathname === "/v1/messages" && !requestUrl.searchParams.has("beta")) {
        requestUrl.searchParams.set("beta", "true");
    }
    return input instanceof Request
        ? new Request(requestUrl.toString(), input)
        : requestUrl;
}

Old version only tacked ?beta=true onto already-correct paths. Both the /messages and /v1/messages code paths now converge on /v1/messages?beta=true.

5. What deliberately was not changed

  • CCH_SALT, CCH_POSITIONS, computeVersionSuffix — byte-identical to real Claude Code.
  • REQUIRED_BETAS (oauth-2025-04-20, interleaved-thinking-2025-05-14) — correct for OAuth flow.
  • CLIENT_ID 9d1c250a-e61b-44d9-88ed-5944d1962f5e — matches the client ID baked into the real Claude Code binary, so the OAuth token is indistinguishable from a native one.
  • mcp_-prefixed tool names — tested in isolation, classifier ignores them.
  • No debug console.error left behind.

Net runtime behaviour

On every Anthropic API call the plugin now:

  1. Refreshes the OAuth token if needed (unchanged).
  2. Rewrites api.anthropic.com/messages or /v1/messagesapi.anthropic.com/v1/messages?beta=true.
  3. Sets user-agent: claude-cli/2.1.117 (external, cli), drops x-api-key, sets authorization: Bearer <token>, merges required OAuth / interleaved-thinking beta flags.
  4. Prepends system block: "x-anthropic-billing-header: cc_version=2.1.117.<3-char-hash>; cc_entrypoint=cli; cch=00000;"
  5. Prepends another system block: "You are Claude Code, Anthropic's official CLI for Claude."
  6. Sanitizes every remaining system block (strips You are OpenCode paragraphs, opencode.ai / anomalyco anchors, Workspace root folder line, anthropic/ model-id prefix, Skills lines; rewrites Working directoryPrimary working directory and Is directory a git repoIs a git repository to match Claude Code's env block).
  7. Prefixes tool names with mcp_ outbound, unprefixes them on the streamed response (unchanged).

Verified end-to-end: Opus 4.7 returns real completions through an opencode server over OAuth.

Install

./install.sh

Or manually:

cp dist/index.js \
   ~/.cache/opencode/packages/op-anthropic-auth@0.1.1/node_modules/op-anthropic-auth/dist/index.js

Then restart opencode (kill the server and relaunch).

Revert

install.sh makes a .bak the first time it runs:

cp ~/.cache/opencode/packages/op-anthropic-auth@0.1.1/node_modules/op-anthropic-auth/dist/index.js.bak \
   ~/.cache/opencode/packages/op-anthropic-auth@0.1.1/node_modules/op-anthropic-auth/dist/index.js

Caveats

  • Fragile. The sanitizer regex-matches opencode's current prompt text. If opencode's upstream changes the wording, a new unique phrase can re-trip the classifier and bring the 400 back. Same if Anthropic tightens classifier signals. If that happens, re-bisect: capture the outgoing system text, replay against api.anthropic.com/v1/messages?beta=true with your token, binary-search until a single line flips the response from 400 to 200.
  • Not upgrade-safe. ~/.cache/opencode/packages/ is a cache directory. opencode upgrade or a plugin reinstall wipes it. You have to re-run install.sh after anything that refetches the op-anthropic-auth@0.1.1 tarball. Long term: fork the plugin into your own npm package and pin it in opencode.json.

Files

File Purpose
dist/index.js Patched plugin. Drop-in for op-anthropic-auth@0.1.1/dist/index.js.
install.sh Copies dist/index.js into the opencode cache, backing up the original once.

About

Drop-in patched dist/index.js for op-anthropic-auth@0.1.1: fixes Anthropic third-party classifier 400s and /messages vs /v1/messages path mismatch in opencode

Resources

Stars

18 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages