Skip to content

claude-code provider: Anthropic third-party gate fingerprints OpenCode's env instruction ('Is directory a git repo') — main sessions billed to extra usage #1819

Closed

Description

@dezren39

Anthropic third-party gate classifies OpenCode as a third-party app on the claude-code provider (env-instruction fingerprint)

Provider: claude-code (anomalyco#6117 native wire emulation) · Evidence: fleet laptop, 2026-11-06, deployed binary 2.0.30+1add729, Claude Code CLI credential (subscription OAuth).

Symptom

Every real OpenCode session request through the claude-code provider is refused by Anthropic:

HTTP 400 invalid_request_error
"Third-party apps now draw from your extra usage, not your plan limits. Add more at claude.ai/settings/usage and keep going."

while the SAME credential succeeds for: the real claude CLI, minimal prompts in the provider's exact wire shape, and even captured OpenCode requests with one system-prompt line removed (below). Title-generation requests (no env instruction) pass; main session requests fail.

Isolation (all against POST https://api.anthropic.com/v1/messages, provider's captured wire shape)

Wire capture of the deployed binary (local forwarding proxy) showed the main session request carries system blocks: [0] claude-code identity, [1] OpenCode harness prompt, [2] model info, [3] Code Mode catalog + skills + date + <env> block, [4] worktree note, [5] session note. Bisecting the captured body against the live API:

  • Dropping block [3] → 200. Dropping block [1] instead → still 400.
  • Within block [3], the sole load-bearing trigger is the environment instruction emitted by packages/core/src/instructions/builtins.ts:43:
    Is directory a git repo: yes|no
    in the <env> block that follows Today's date: … / "Here is some useful information about the environment you are running in:" / Working directory: / Workspace root folder:.
  • Full captured body with ONLY that one line removed → 200, correct reply text. Same body with the value flipped (no→yes or yes→no) → still 400. Every other env line (Working directory, Workspace root folder, Platform, the /tmp guidance line) individually passes; the git-repo line alone or in pairs passes — it fires only in the complete env block, i.e. Anthropic's classifier fingerprints the env block by that anchor line.
  • A Claude-Code-shaped prompt (large system prompt + real tool definitions) in the identical wire shape → 200. The gate is content classification, not headers/UA/betas (swapping User-Agent between opencode/… and claude-cli/2.1.306 changes nothing).

Reading

Anthropic's third-party detection for subscription OAuth keys on the harness's own env instruction: any client whose system prompt contains OpenCode's <env> block is classified "third-party app" and billed to extra usage (which this account does not have funded) instead of plan limits. The native claude-code emulation reproduces the CLI's wire faithfully — the request is rejected because of what OUR harness puts in the prompt, not because of the provider's protocol shape.

Options

  1. Per-provider instruction shaping: when the target provider is claude-code, omit (or rephrase) the Is directory a git repo line — or the whole <env> block — from builtin instructions. Verified sufficient at the API level (removal → 200 with the rest of the harness prompt, Code Mode catalog, and tools intact). Cost: the model loses one env fact on this provider only.
  2. Fund extra usage at claude.ai/settings/usage (account-side; Drew's call, changes billing for ALL third-party-classified traffic).
  3. Accept the provider as title-gen/small-prompt-only — not viable; main sessions are the point.

Option 1 looks like a one-line change in builtins.ts gated on provider, or an instruction-layer filter in the session runner for the claude-code provider id. Filing for the design call; the API-level fix is proven.

Activity

  1. added a commit that references this issue on Nov 6, 2026
  2. dezren39 commented on Nov 6, 2026

    @dezren39
    Author

    Fixed by #1826 — MERGED to main as 22c0a6ddbee19878e8e51bde432e995f75ccdd2a.

    Shape: route-scoped strip in the Claude Code Messages protocol (packages/ai/src/protocols/claude-code-messages.ts): system parts have the whole line ^\s*Is directory a git repo: (yes|no)\s*$ removed before the body is lowered. The core core/environment builtin is unchanged and every other provider keeps the full env block — the omission exists only on the claude-code route, next to the identity-block prepend that already shapes system parts there.

    Verification: claude-code.test.ts 11/11 pass (4 new tests; oracle = this issue's live-API bisection — compiled env block exact-match minus the line for both values, byte-identical passthrough when absent, scope proof against the API-key Anthropic route, generated-placement invariant test); anthropic-messages.test.ts 76/76; bun run typecheck clean.

    Deploy: rides the developing-today/code flake pin bump to fork main 22c0a6ddbe (with the #1813 endpoint fix) on the next nixos-rebuild switch. Until then the deployed binary (2.0.30+1add729) still fails earlier at the #1813 endpoint defect, so the gate itself is not yet reachable in production.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions