@cortexkit/opencode-anthropic-auth
v1.23.0
Published
Claude Pro/Max OAuth support for both [OpenCode](https://opencode.ai) and [Pi](https://pi.dev), maintained by CortexKit.
Readme
CortexKit Anthropic Auth for OpenCode and Pi
Claude Pro/Max OAuth support for both OpenCode and Pi, maintained by CortexKit.
This repo is a Bun workspace monorepo with two user-facing integrations and one shared core package. The OpenCode package is a CortexKit-maintained fork of the original @ex-machina/opencode-anthropic-auth plugin. The Pi package is a native Pi provider extension that overrides Pi's built-in Anthropic provider. Both integrations share the same Anthropic OAuth, fallback-account, quota, prompt-cache, relay, dump, and request-signing logic through @cortexkit/anthropic-auth-core.
Packages
| Package | Agent | Purpose |
| --- | --- | --- |
| @cortexkit/opencode-anthropic-auth | OpenCode | OpenCode plugin and CLI for Claude OAuth, request rewriting, fallback accounts, quotas, cache controls, dumps, and relay setup. |
| @cortexkit/pi-anthropic-auth | Pi | Pi package/extension that registers a CortexKit Anthropic provider under Pi's built-in anthropic provider ID. |
| @cortexkit/anthropic-auth-core | Shared | Reusable OAuth, account, quota, cache, relay, dump, SSE, and request-signing logic used by both integrations. |
Support matrix
| Capability | OpenCode | Pi |
| --- | --- | --- |
| Primary Claude Pro/Max OAuth | OpenCode /connect anthropic | Pi /login anthropic |
| Provider integration point | OpenCode plugin fetch/request transform | Pi registerProvider("anthropic") provider override |
| Sidecar config | ~/.config/opencode/anthropic-auth.json | ~/.pi/agent/anthropic-auth.json |
| Runtime state | ~/.config/opencode/anthropic-auth-state.json | next to the Pi sidecar as anthropic-auth-state.json |
| Commands | /claude-cache, /claude-cachekeep, /claude-prime, /claude-start, /claude-routing, /claude-fast, /claude-quota, /claude-dump, /claude-killswitch | /claude-cache, /claude-cachekeep, /claude-prime (status only), /claude-routing, /claude-fast, /claude-quota, /claude-dump |
| Fallback accounts, quota routing, killswitch, relay, dumps, fast mode | Supported | Supported through the same shared core and Pi sidecar |
What CortexKit adds over the original plugin
- Fallback Claude accounts: keep each agent's normal Anthropic login as the primary account, then route to ordered fallback OAuth accounts on auth/quota/rate-limit failures.
- Routing modes: use
/claude-routing sticky-balancedto distribute cold sessions by current OAuth quota headroom while keeping each session on one account, or choosemain-first/fallback-firstordering. - Quota-aware routing: skip main or fallback accounts when their 5-hour or 7-day Claude quota falls below your configured minimum.
- Persistent Claude cache controls: manage Anthropic 1-hour prompt caching from
/claude-cachewith explicit, automatic, or hybrid modes. - Cache keepalive: use
/claude-cachekeep alwaysor/claude-cachekeep HH-HHto pre-warm hybrid cache anchors for active sessions before the 1-hour TTL expires. - Quota window priming: opt in with
/claude-prime onto start each 5-hour quota window about one minute after it resets instead of waiting for the next normal prompt. - Lane start (OpenCode only): use
/claude-startto fire one synthetic, one-token turn through the current session's normal model, agent, variant, quota, routing, cache, and request pipeline. - Fast mode toggle: use
/claude-fast on|offto request Anthropic fast mode for supported Opus models (claude-opus-4-6,claude-opus-4-7,claude-opus-4-8,claude-opus-5, andclaude-opus-5-5). - Adaptive reasoning visibility: request summarized adaptive thinking for Claude Fable 5/5.1, Mythos 5/5.1, Opus 5, and Opus 5.5. OpenCode exposes native
low,medium,high,xhigh, andmaxeffort variants for Fable 5.1, Opus 5, and Opus 5.5. On Opus 5.5, thinking is always on (adaptive summarized) per the model specification. OAuth Fable 5.1 sessions can change effort between turns without rewriting the cached prefix. - Fable/Opus 5 and 5.5 safety fallback: eligible OAuth requests try Anthropic's server-side safety fallback first. The plugin preserves Anthropic's fallback conversation boundary across OpenCode history and automatically starts its deterministic 10-response Opus 4.8 recovery if the response still ends in refusal. The TUI sidebar and OpenCode Desktop report the active target model and restoration. Set
OPENCODE_ANTHROPIC_AUTH_FALLBACK_MODE=legacyto bypass the server policy and use client-side recovery exclusively. - Live quota visibility: use
/claude-quotato see main and fallback quota state, reset times, and refresh errors. - Host-local quota feed: optionally publish sanitized response-header quota observations so another local CortexKit process can reuse fresh account state without polling Anthropic's rate-limited usage endpoint.
- Killswitch: per-account hard-block thresholds that stop requests before hitting Anthropic's rate limits, with synthetic 429 retry-after when all accounts are exhausted.
- User-owned Cloudflare relay: optionally provision your own Worker relay to reduce repeated client upload bytes for large OpenCode or Pi requests.
- Claude-compatible request hardening: Claude Code 2.1.258 identity and final-body billing signing, user-turn prompt and request lineage across tool loops, live-session-stable billing suffixes, Fable 5.1 thinking-prefix recovery, safer token refresh persistence, replay-safe fallback retries, and subagent cache isolation.
What these integrations do
- Let OpenCode and Pi use Claude Pro/Max OAuth credentials instead of an Anthropic API key.
- In OpenCode, intercept the final Anthropic request and rewrite it into the Claude-compatible shape expected by Anthropic OAuth access.
- In Pi, replace Pi's built-in Anthropic provider with a CortexKit provider override that uses the same Claude-compatible request path.
- Add Claude billing headers with stable
cc_version, user-turncc_prompt_id/cc_prev_reqlineage, and body-derivedcchsigning. - Support fallback Claude accounts stored in a local per-agent sidecar file.
- Keep fallback OAuth tokens fresh in the background.
- Apply quota thresholds before routing to main or fallback accounts.
- Add
/claude-cache,/claude-cachekeep,/claude-prime,/claude-start,/claude-fast,/claude-quota, and/claude-dumpcommands. - Optionally relay large requests through a Cloudflare Worker owned by the user.
Setup wizard (recommended)
Run the interactive setup wizard:
bunx @cortexkit/opencode-anthropic-auth setupThe wizard detects OpenCode, Pi, and Claustrum, configures plugin entries, and optionally activates Claustrum custody.
Install
OpenCode
Add the OpenCode plugin to your OpenCode configuration:
{
"plugin": ["@cortexkit/opencode-anthropic-auth"]
}Pinning is strongly recommended for any OpenCode plugin:
{
"plugin": ["@cortexkit/[email protected]"]
}After changing plugin config, restart OpenCode.
[!TIP] If OpenCode keeps using an old build, clear OpenCode's plugin cache with
rm -rf ~/.cache/opencodeand restart.
Pi
Install the Pi package with Pi's package manager:
pi install npm:@cortexkit/[email protected]For an unpinned install:
pi install npm:@cortexkit/pi-anthropic-authTo try it for one run without adding it to Pi settings:
pi -e npm:@cortexkit/pi-anthropic-authThe Pi package registers a CortexKit Anthropic provider extension under Pi's built-in anthropic provider ID. After installation, start or restart Pi and authenticate with Pi's normal login command:
/login anthropicPi package state lives separately from OpenCode in:
~/.pi/agent/anthropic-auth.jsonOverride the path with PI_ANTHROPIC_AUTH_FILE. The package also respects PI_AGENT_DIR when deriving the default sidecar path.
Primary account authentication
Each integration keeps the host agent's normal Anthropic login as the primary account.
For OpenCode, use OpenCode's Anthropic auth flow:
/connect anthropicThe primary account remains OpenCode's built-in anthropic auth entry. The OpenCode plugin intercepts final Anthropic requests and supplies the OAuth headers and request transforms needed for Claude Pro/Max access.
OpenCode's upstream authentication options are still supported:
- Claude Pro/Max OAuth through
claude.ai. - Anthropic Console OAuth that creates an API key.
- Manually entered Anthropic API key.
For Pi, install the Pi package, restart Pi, then use Pi's Anthropic login flow:
/login anthropicThe Pi package registers under Pi's built-in anthropic provider ID and stores primary OAuth credentials through Pi's normal credential flow. CortexKit package state, fallback accounts, cache mode, dump mode, and relay config live in the Pi sidecar file.
Sidecar config
OpenCode package state lives in:
~/.config/opencode/anthropic-auth.jsonOverride the OpenCode path with OPENCODE_ANTHROPIC_AUTH_FILE.
Pi package state uses the same JSON shape but a separate file:
~/.pi/agent/anthropic-auth.jsonOverride the Pi path with PI_ANTHROPIC_AUTH_FILE.
Example:
{
"version": 1,
"main": { "type": "opencode", "provider": "anthropic" },
"fallbackOn": [401, 403, 429],
"routing": {
"mode": "main-first"
},
"refresh": {
"enabled": true,
"intervalMinutes": 10,
"refreshBeforeExpiryMinutes": 240
},
"quota": {
"enabled": true,
"checkIntervalMinutes": 5,
"minimumRemaining": {
"five_hour": 10,
"seven_day": 20
},
"failClosedOnUnknownQuota": true,
"showToasts": false
},
"quotaHeaderFeed": {
"enabled": false
},
"claustrum": {
"accounts": {}
},
"killswitch": {
"enabled": false,
"main": { "five_hour": 5, "seven_day": 10, "scoped": 0 },
"accounts": {}
},
"claudeCache": {
"enabled": false,
"mode": "explicit"
},
"cacheKeep": {
"enabled": false,
"always": false,
"startHour": 9,
"endHour": 23
},
"dump": {
"enabled": false
},
"claudeFast": {
"enabled": false
},
"thinkingBinding": {
"prefixMismatchBehavior": "account-default"
},
"costZeroing": {
"enabled": true
},
"relay": {
"enabled": false,
"url": "https://opencode-anthropic-relay.example.workers.dev",
"token": "relay-shared-secret",
"transport": "http",
"fallbackToDirect": true
},
"accounts": []
}The routing block controls /claude-routing, claudeCache controls /claude-cache, cacheKeep controls /claude-cachekeep, and claudeFast controls /claude-fast. OpenCode zeroes Anthropic OAuth model costs by default because OAuth usage is quota-based; set costZeroing.enabled to false only if you want OpenCode to display the provider's model pricing instead. Set quota.showToasts to true to opt into OpenCode quota toast notifications after quota refreshes. The main field identifies OpenCode's primary auth entry; Pi keeps primary OAuth credentials in Pi's own credential store, but uses the same sidecar shape for CortexKit settings and fallback account labels.
thinkingBinding.prefixMismatchBehavior controls replayed signed/redacted thinking on OAuth Fable 5.1 requests. account-default (the default) sends no explicit prefix-mismatch override. Use error to make prefix changes fail deterministically during compaction testing, or drop_block to ask Anthropic to discard mismatched thinking blocks. The beta header is added only when an explicit behavior is configured and replayable thinking is present.
quotaHeaderFeed.enabled is an OpenCode-only, restart-required opt-in. It publishes only allowlisted quota-window values, an opaque account reference, an observation timestamp, and the configured OAuth-account count; it never publishes tokens, raw headers, request bodies, model IDs, or refresh errors. Per-process lease files use owner-only permissions under $TMPDIR/opencode-anthropic-auth/quota-header-feed, expire after three minutes, and can be redirected with OPENCODE_ANTHROPIC_AUTH_QUOTA_FEED_DIR.
claustrum.accounts remains loadable only so configurations can safely downgrade to older releases; it does not gate custody per account. Capability handles are bearer credentials and must not be copied into the public config file.
Runtime data is stored separately in anthropic-auth-state.json: fallback OAuth tokens, API-route keys, token refresh backoff, quota snapshots, and quota API backoff. Sticky session assignments use anthropic-auth-routing-state.json and store only SHA-256 hashes of session IDs. Background refresh and quota checks write only runtime state, so editing anthropic-auth.json does not get overwritten by another running plugin instance.
OpenCode lane-start setting
Fallback accounts
Fallback accounts are separate Claude OAuth accounts or Anthropic-compatible API-key routes managed by this plugin. By default, the main account is tried first unless quota policy says it is currently unusable. Fallbacks are then tried in sidecar order when the primary request returns a configured fallback status.
Use /claude-routing fallback-first to prefer usable fallback accounts before the main account, /claude-routing main-first to restore the default, or /claude-routing sticky-balanced to allocate each new session according to spendable 5-hour, 7-day, and matching model-scoped OAuth quota headroom. The command persists routing.mode and takes effect on the next request without restarting. /claude-routing reset clears only the current session's assignment so its next request is allocated again.
sticky-balanced normalizes quota headroom by time until reset and tracks weighted initial-prompt deficit until the next quota refresh. Assignments are persisted by hashed session ID in anthropic-auth-routing-state.json, shared safely across OpenCode plugin instances and restarts. Active hybrid CacheKeep sessions seed their current route when the mode is first enabled. Changing the user-selected model discards the old assignment and its model-specific CacheKeep preference before quota-based reselection; transparent safety-recovery model changes retain the original account.
Affinity survives network/relay failures, 5xx responses, unconfirmed 429s, quota-probe failures, and relative quota changes. Confirmed 7-day or matching model-scoped exhaustion migrates a session. Confirmed 5-hour exhaustion migrates only when more than 15 minutes remain; otherwise the plugin retains the route and returns Retry-After. Fable content-filter recovery keeps Fable, Opus 4.8, and Fable prewarms on the same sticky account. Direct Opus sessions first prefer an otherwise usable account whose Fable quota is exhausted.
Default fallback statuses:
[401, 403, 429]Add and inspect OpenCode fallback accounts with the CLI:
bunx @cortexkit/opencode-anthropic-auth login personal-alt
bunx @cortexkit/opencode-anthropic-auth api add kie-opus
bunx @cortexkit/opencode-anthropic-auth listPrefer npm? Use npx -y @cortexkit/opencode-anthropic-auth ... with the same subcommands.
API fallback routes use Authorization: Bearer <key> by default and rewrite requests to the configured Anthropic-compatible base URL. For Kie, use https://api.kie.ai/claude as the base URL; the plugin appends /v1/messages automatically. API-key routes are only eligible after direct evidence that the main OAuth quota is exhausted: a fresh token-bound quota snapshot at 0% remaining, or an actual main OAuth model response with HTTP 429 / streaming rate-limit error followed by a live quota check that confirms 0% remaining. Low-but-nonzero quota, stale cached quota, unconfirmed 429s, 401, or 403 do not trigger API-key routes. API-route keys are stored in anthropic-auth-state.json, while anthropic-auth.json keeps the route label, type, enabled flag, base URL, and auth-header mode.
OpenCode cost accounting stays simple: when the native Anthropic auth entry is OAuth, OpenCode sees zero-cost Claude OAuth models by default. If a request falls through to an API-key route, token accounting is still recorded, but OpenCode's built-in dollar cost is not route-aware. Advanced users can set costZeroing.enabled to false to show Anthropic model pricing for OAuth sessions too.
For Pi fallback accounts, write the same account JSON shape to ~/.pi/agent/anthropic-auth.json. The CLI helper currently lives in the OpenCode package, so you can also point it at Pi's sidecar path when logging in a fallback account:
OPENCODE_ANTHROPIC_AUTH_FILE="$HOME/.pi/agent/anthropic-auth.json" \
bunx @cortexkit/opencode-anthropic-auth login personal-altFallback retries are only attempted when the request body is safely replayable. If the original body is non-replayable or already consumed, the plugin returns the primary response unchanged.
Token refresh
Fallback OAuth tokens refresh in the background so idle accounts do not expire before they are needed. Refresh token rotation is persisted immediately. The plugin also re-reads the latest sidecar account before refreshing, which avoids using stale refresh-token snapshots when multiple background paths run close together. Persisted refresh backoffs are bound to the refresh token that produced them, so replacing a rejected credential immediately clears its stale latch.
If Anthropic reports invalid_grant, that account must be logged in again. /claude-account reset-backoff manually clears the main account's refresh backoff and its matching quota backoff.
Claustrum manifest service
In global Claustrum mode, /claude-account claustrum serves OAuth routes that have a binding present in the Claustrum handle manifest; /claude-account local returns authority to local OAuth refresh. The vault serves credentials, and the plugin discovers only its own provider block.
The request path reads only a resident in-memory credential. Startup warming and periodic reconciliation perform vault I/O and keep idle credentials refreshed. In Claustrum mode, a cold or unavailable vault must return a typed provider-unavailable response; it does not fall back to the sidecar credential path. That cold-route behavior lands with the dedicated cold-route task. Vault-served 401 reports carry the exact record version and response provenance, including relay-stream 401s, so a sidecar-served failure cannot invalidate a healthy vault credential. /claude-account and the account modal show manifest-binding presence, current vault service, and vault reauthentication state without exposing capability handles.
After a new Anthropic credential is explicitly bound to anthropic-auth, a running plugin watches the manifest and automatically creates its secret-free routing row; the same reconciliation runs during startup. Enrollment requires an exact live credential.get match for both the manifest credential ID and provider account UUID, persists only a custody tombstone plus identity metadata, and immediately refreshes the new route's quota so sticky-balanced routing does not wait for the next background interval. Existing disabled rows remain disabled. To stop routing without changing the manifest, disable the account; removing a manifest-bound row is refused until its binding is removed.
Claustrum mode also starts the future scoped-discovery enrollment ceremony under the host identity anthropic-auth-opencode. The plugin writes the request secret before proposing, shares one locked ceremony across every OpenCode project process, and displays the pending request ID and exact approval command in /claude-account. Approval is persisted atomically as an owner-only token at $XDG_STATE_HOME/cortexkit/anthropic-auth/opencode-enrollment.json (default ~/.local/state/cortexkit/anthropic-auth/opencode-enrollment.json). Grant that identity only category:anthropic-native; never grant the broader llm-provider category. This release does not spend the enrollment token yet: model requests continue through the existing capability-handle manifest until the scoped-discovery cutover lands. /claude-account enrollment-reset retries only a denied or locally blocked ceremony and refuses to discard pending or approved authority.
If Claustrum has replaced the main host credential with its provider-bound tombstone, the plugin rejects refresh locally without contacting Anthropic or persisting a permanent invalid_grant state. API-key routes are unaffected.
Quota-aware routing
When quota.enabled is true, the plugin checks Anthropic's OAuth usage endpoint and applies the configured remaining-quota thresholds to both main and fallback accounts.
Example:
"minimumRemaining": {
"five_hour": 10,
"seven_day": 20
}With this config, an account is skipped when it has less than 10% remaining in the 5-hour window or less than 20% remaining in the 7-day window. The aliases 5h and 1w are also accepted.
Main-account quota is cached. If the main account is known to be exhausted, the plugin skips it until the relevant reset time. If the cached main quota is stale but usable, the request proceeds and quota refresh happens in the background.
Show current quota state:
/claude-quotaIn OpenCode, this includes the main Anthropic account and sidecar fallback accounts. In Pi, the command reports sidecar fallback account quota state from ~/.pi/agent/anthropic-auth.json.
Reset times are rendered as relative durations, such as resets in 10m or resets in 1h 15m.
Safety fallback
Eligible Fable 5/5.1 and Opus 5 OAuth requests try Anthropic's server-side safety fallback first. The plugin sends fallbacks: "default" with Anthropic's server-side fallback beta, preserves fallback conversation boundaries in OpenCode history, and reports model handoffs and restoration in the TUI sidebar or OpenCode Desktop. Follow-up requests may remain on Anthropic's selected fallback model for approximately one hour.
If an eligible OAuth response still ends in refusal, CortexKit automatically starts its deterministic 10-successful-response Opus 4.8 recovery, prewarms the selected source model without the server-fallback opt-in, and then restores that model. If the refused response already completed a tool call, the plugin preserves that call and continues with its existing tool result instead of replaying the tool.
Custom API-key routes do not receive the server-side fallback beta or request field.
To bypass Anthropic's server policy and use deterministic client recovery from the first refusal, start OpenCode with:
OPENCODE_ANTHROPIC_AUTH_FALLBACK_MODE=legacy opencodeLegacy mode routes a refused Fable 5/5.1 or Opus 5 session through Opus 4.8 for 10 successful responses, prewarms the selected source model's cache, and then restores that model without first opting into Anthropic's server policy. Unset the variable and restart OpenCode to return to the default server-first policy with the same client recovery as a backstop.
Killswitch
The killswitch is a per-account hard-block that stops requests when remaining quota drops below configured thresholds, even if Anthropic's API would still accept them. Unlike minimumRemaining (which routes to fallback accounts), the killswitch removes accounts from the routing pool entirely.
Add a killswitch block to the sidecar config:
"killswitch": {
"enabled": true,
"main": {
"five_hour": 5,
"seven_day": 10,
"scoped": 0
},
"accounts": {
"work-alt": {
"five_hour": 10,
"seven_day": 20,
"scoped": 0
}
}
}Thresholds are remaining-percent values. With five_hour: 5, the account is killed when less than 5% of the 5-hour quota window remains. The optional scoped threshold applies to matching model-scoped quota windows (for example Fable weekly quota) and blocks when the scoped remaining percent is at or below the threshold. Accounts without an entry in accounts fall back to the main thresholds. The aliases 5h and 1w are also accepted.
Behavior:
- When an account is killed, it is skipped during routing. Surviving accounts are tried instead.
- When all accounts (main and all enabled fallbacks) are killed, the plugin returns a synthetic 429 response with a
retry-afterheader set to the earliest quota reset time across all accounts. - On the first request after restart, the plugin eagerly fetches main quota so the killswitch evaluates immediately.
/claude-quotashows killswitch status and per-account killed/active state.
Manage the killswitch from inside OpenCode:
/claude-killswitch — show status and command cheatsheet
/claude-killswitch on — enable with current or default thresholds
/claude-killswitch off — disable
/claude-killswitch set all:5,10 — set all accounts to 5h≥5%, 1w≥10%
/claude-killswitch set main:3,8,0 — set main to 5h≥3%, 1w≥8%, scoped≤0%
/claude-killswitch set main:3,8,0 work-alt:5,10,0 — per-account thresholdsChanges made with /claude-killswitch are persisted to the sidecar config.
Claude prompt cache control
Both OpenCode and Pi packages add a slash command for Anthropic's 1-hour ephemeral prompt-cache TTL:
/claude-cache
/claude-cache on
/claude-cache off
/claude-cache mode explicit
/claude-cache mode automatic
/claude-cache mode hybridWithout arguments, /claude-cache shows the current setting.
Modes:
explicitkeeps OpenCode's explicit cache breakpoints and addsttl: "1h"to them.automaticremoves block-level cache controls and sends a top-levelcache_controlobject.hybrid(recommended) removes top-level automatic caching and uses explicit anchors for Magic Context's leading history plus a moving latest-user boundary. When the first message has multiple cacheable content blocks, hybrid anchors both the first and last block so a stable leading history block remains cached even if a volatile trailing block changes; otherwise it anchors the first two messages. It keeps the last stable system block in normal turns, and uses that slot for the previous user boundary when a tool-heavy step would exceed Anthropic's 20-block lookback.
In OpenCode, subagent requests do not receive 1-hour TTL caching. The plugin detects child sessions through OpenCode's x-parent-session-id header, strips that internal header before forwarding to Anthropic, and leaves default ephemeral caching in place for those requests.
Cache keepalive
/claude-cachekeep keeps recently used hybrid-mode session caches warm while the agent process is running:
/claude-cachekeep
/claude-cachekeep always
/claude-cachekeep 09-23
/claude-cachekeep offalways keeps tracked sessions warm continuously for as long as the OpenCode process remains open. Hour ranges use local 24-hour time and are start-inclusive/end-exclusive: 09-23 runs from 09:00 until 22:59, and overnight windows such as 23-09 are accepted.
Cache keepalive only tracks requests when /claude-cache is enabled in hybrid mode. For each active session seen in the configured schedule, the package keeps an in-memory clone of the latest rewritten Anthropic request and sends a non-streaming max_tokens: 0 pre-warm request about five minutes before the 1-hour cache entry would expire. Running /claude-cachekeep without arguments lists every live tracked session across OpenCode projects and plugin processes.
Request bodies, headers, and tokens remain in memory. A lease-backed file under the system temporary directory shares only session IDs and cache timing for cross-instance status; records from stopped processes disappear from status after about three minutes.
Pre-warm requests preserve explicit cache anchors but remove response-only fields that Anthropic rejects with max_tokens: 0, such as streaming, enabled thinking, structured output format, and forced/any tool choice. The feature works only while OpenCode or Pi is running and the machine is awake, and cache writes are still billed when the cache entry is no longer warm.
Claude quota window priming
Quota-window priming is off by default. OpenCode controls it with:
/claude-prime
/claude-prime on
/claude-prime offWhen enabled, the plugin watches each OAuth account's 5-hour quota reset. About one minute after a confirmed reset, it sends one minimal claude-haiku-4-5 request so the next window starts without waiting for a normal prompt. Usage is measured from response accounting.
Scheduled fires happen only after the quota window has reset. If an idle account has no cached reset time, the plugin sends one bootstrap request to establish its first observed window. Atomic temporary-file claims ensure that multiple OpenCode processes sharing the same account config send only one request per account and reset.
Prime marker identities live in anthropic-auth-state.json. Plugin-owned refresh rotations preserve the main account's lineage, while a host credential replacement creates a new lineage. An existing main lineage without a refresh-token binding attaches to the current credential on its first check without changing identity. On upgrade, an existing fallback account receives an identity during its first prime check; that one-time marker change can send one extra request in the current window.
Pi exposes /claude-prime as a status-only command. Its on and off arguments are ignored; enable or disable priming from OpenCode.
OpenCode lane start
/claude-start is an OpenCode-only command. It queues one synthetic turn for the current session:
/claude-startThe bare command fires immediately. The synthetic prompt uses the session's current model, agent, and variant, then travels through the ordinary quota, routing, cache, relay, signing, and response pipeline. OpenCode shapes that OAuth request to max_tokens: 1 while keeping streaming enabled, and correlates the request by its synthetic message ID. A queued modal is a request to start the turn, not a provider-success claim.
[!IMPORTANT] This is a real billed request. The one-token limit reduces generated output only; Anthropic still charges for prompt-cache reads and any new cache writes. Use it when you plan to resume the session before the refreshed cache expires.
Pi does not expose this command.
Cache diagnostics (beta)
The cache-diagnosis-2026-04-07 beta is measure-only. It asks Anthropic to report prompt-cache diagnostics; it does not change cache controls or routing. OpenCode captures the provider's top-level response ID as an opaque string and sends it as diagnostics.previous_message_id on the next request in the same session. The first request sends null.
The MC-CACHE-DIAG line is a versioned, one-line JSON record. Records and their beta side-channel are emitted only at debug level; use /claude-logging debug to capture them. Because emission is gated on the runtime log level, an empty record stream is ambiguous between a quiet system and a level below debug — liveness checks must cross-reference an independent activity witness (billing, usage rows, or dumps) instead of reading silence as absence of cache traffic. Version 2 contains these fields:
| Field | Type | Source |
| --- | --- | --- |
| v | 2 | Capture schema |
| source | string | Observation path; known values are "turn", "start", and "prewarm_cachekeep", but consumers must tolerate future values |
| synthetic | boolean | Whether this observation was generated by plugin machinery rather than a real turn |
| account_id | string | Persisted plugin-internal OAuth account identifier used by routing and the sidebar; stable across restarts and token refreshes, so consumers may key timelines on it. Opaque mixed key space (the main account is a sentinel string, fallbacks are UUIDs) — never validate its shape |
| betas_hash | 16-character lowercase hex | xxHash64 (seed 0) of the sorted anthropic-beta list actually sent, truncated to its first 16 hexadecimal characters |
| requested_model | string, optional | Sent request-body model, present only when it differs from the served Anthropic response model |
| session_id | string | OpenCode session affinity |
| ts_ms_received | integer | Local response receipt time |
| model | string | Anthropic response |
| is_subagent | boolean | Original request context |
| ttl_sent | "1h" \| "5m" \| null | Last valid cache breakpoint in the sent body |
| cache_read | number | usage.cache_read_input_tokens |
| cache_creation | number | usage.cache_creation_input_tokens |
| input_tokens | number | usage.input_tokens |
| ephemeral_5m_tokens | number | usage.cache_creation.ephemeral_5m_input_tokens |
| ephemeral_1h_tokens | number | usage.cache_creation.ephemeral_1h_input_tokens |
| message_id | string | Anthropic response top-level id |
| previous_message_id | string | null | Sent diagnostics.previous_message_id |
| diag_state | "absent" \| "server_null" \| "pending" \| "populated" | Anthropic response envelope |
| miss_reason | string, optional | diagnostics.cache_miss_reason.type |
| cache_missed_input_tokens | number, optional | diagnostics.cache_miss_reason.cache_missed_input_tokens |
cache_missed_input_tokens is a byte-derived, pre-tokenization magnitude indicator, not a token count: it can differ from and occasionally exceed input_tokens, so never difference it against usage fields.
All required usage counters must be finite, non-negative numbers. A malformed required counter rejects the whole record rather than emitting a partial receipt.
Consumers classifying these records should check cache_read before miss_reason: cache_read > 0 is a fact and miss_reason is an interpretation, and facts win. On multi-turn agent traffic a populated *_changed reason routinely coexists with a full prefix hit (each turn appends messages), so a populated-first classifier inverts every such record into a false miss.
Known sources map to synthetic as follows:
| source | synthetic |
| --- | --- |
| turn | false |
| start | true |
| prewarm_cachekeep | true |
Lane-start cache observations therefore appear as ordinary MC-CACHE-DIAG records with source: "start"; the row above is the diagnostics-side companion to the /claude-start pipeline described earlier.
synthetic wins on conflict. A disagreement for a known source is an emitter defect: OpenCode writes one warning and still emits the record with the supplied synthetic value. Unknown future sources have no mapping and must not be rejected by consumers.
The first observation of each betas_hash in a process also writes MC-CACHE-DIAG-BETAS {"hash":"…","betas":[…]} at debug level on the cache-diagnostics logger channel. Its sorted beta list makes an observed hash interpretable without reconstructing headers; repeated hashes do not emit another side-channel line.
The trailing space in MC-CACHE-DIAG is load-bearing: a record line has a space after MC-CACHE-DIAG, while the beta side-channel line has a hyphen. Do not trim the delimiter or write a trimming consumer. grep -c "MC-CACHE-DIAG" over-counts because it includes beta lines; count records with grep -c "MC-CACHE-DIAG " or an anchored equivalent.
The four diagnostic states are:
| State | Response shape |
| --- | --- |
| absent | No diagnostics property |
| server_null | diagnostics: null |
| pending | diagnostics.cache_miss_reason: null |
| populated | An object diagnostics.cache_miss_reason with a non-empty string type, including "unavailable" |
Only valid Message envelopes produce an MC-CACHE-DIAG record. Malformed or error responses are not mapped to absent; they retain ordinary response handling and dumps. ttl_sent is reduced from the last valid breakpoint in the actual body sent. A short-gap previous_message_not_found result is also recorded as a canary when the previous provider ID was captured less than five minutes earlier.
unavailable is expected when any prompt-affecting parameter changes between requests. This includes the active Anthropic beta-header set; structured-output requests use a different beta set and can therefore produce unavailable. Diagnostics are scoped to the provider's cache fingerprint, organization, and workspace. Fingerprints expire, so a later request can miss after a long gap even when the body is unchanged. These records measure the result; they do not guarantee a cache hit.
Diagnostics comparison requires a cacheable prefix. Requests below the model's cacheable minimum or without cache breakpoints return diagnostics: null even when the request genuinely changed; consumers must gate interpretation of null on observed cache activity.
diag_state is always a string and never JSON null: absent | server_null | pending | populated.
Version 1 records come from the unversioned-source era and cannot distinguish prewarms from turns; consumers must treat their source as unknown and cannot split machinery from traffic retroactively. Version 2 always states the source.
When request dumps are enabled, each response gets a .response.json artifact containing status and parsed response metadata, but no response content. Lane-start requests are tagged -start-; CacheKeep prewarms retain -prewarm-cachekeep-. If Prime prewarming is enabled in a build that supports it, those artifacts use -prewarm-prime. Treat request bodies and related dump files as sensitive local debugging data.
Claude fast mode
Both OpenCode and Pi packages can persistently request Anthropic fast mode for supported Opus models:
/claude-fast
/claude-fast on
/claude-fast offWhen enabled, supported requests add speed: "fast" to the Anthropic JSON body and include the fast-mode-2026-02-01 beta header. Unsupported models are left at standard speed. Anthropic currently documents fast mode for claude-opus-4-6, claude-opus-4-7, claude-opus-4-8, and claude-opus-5; Claude Fable and Mythos 5/5.1 are not fast-mode models.
Fast and standard speeds do not share prompt-cache prefixes, so switching this setting can cause cache misses.
Estimate cache savings from OpenCode history
The repo includes an OpenCode SQLite analyzer that compares estimated Claude cost under three scenarios: no prompt cache, Anthropic's default 5-minute cache, and this plugin's 1-hour cache mode.
From a repo checkout:
bun run analyze:cache -- --days 7Useful variants:
# Restrict to one OpenCode session
bun run analyze:cache -- --session ses_... --days 4
# Emit machine-readable output
bun run analyze:cache -- --days 7 --json
# Use a non-default OpenCode DB path
bun run analyze:cache -- --db ~/.local/share/opencode/opencode.db --days 30The script reads OpenCode usage data from ~/.local/share/opencode/opencode.db by default. It uses recorded prompt, cache-read, cache-write, and output tokens, then estimates counterfactual 5-minute and no-cache costs from the same turns. Its pricing table includes Claude Opus 4.5 through 4.8. The default 5-minute expiry threshold is 5 minutes; override it with --idle-threshold-min <minutes> if needed.
This analyzer is OpenCode-specific because it reads OpenCode's local message database.
Optional Cloudflare relay
The relay is opt-in and user-owned. CortexKit does not run shared relay infrastructure for this plugin.
When enabled, the package sends large Anthropic request bodies to a Cloudflare Worker that you own. The first request for a session sends a full body; later requests send compact patches keyed by session affinity (x-session-affinity in OpenCode, Pi's stream sessionId in Pi). The Worker reconstructs the full Anthropic /v1/messages request and streams Anthropic's SSE response back to the client.
New relay setups default to HTTP transport:
"transport": "http"HTTP is the safest release default and still sends compact full-sync/patch payloads through your Worker. WebSocket is available as an opt-in persistent session transport:
"transport": "websocket"WebSocket mode uses protocol v2 on /ws, keeps one connection per OpenCode x-session-affinity, and serializes same-session requests until the previous stream finishes. Existing deployed Workers must be redeployed with this package's current Worker script before "transport": "websocket" will work; older relay Workers only understand the legacy protocol.
Set up a relay in your Cloudflare account with the OpenCode package CLI:
CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... bunx @cortexkit/opencode-anthropic-auth relay setupOr with npm:
CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... npx -y @cortexkit/opencode-anthropic-auth relay setupThe setup command:
- Creates a Cloudflare KV namespace.
- Uploads the relay Worker module.
- Generates a relay shared secret.
- Enables the Worker.
- Writes the local
relayblock to~/.config/opencode/anthropic-auth.json.
For Pi, copy the generated relay block into ~/.pi/agent/anthropic-auth.json if you want the Pi package to use the same user-owned Worker.
The Cloudflare API token is used only during setup and is not stored by the plugin. Re-running setup with the same Worker name uploads the current Worker script again; this is how existing relay Workers are upgraded for protocol changes such as WebSocket v2.
If relay setup or transport fails before streaming begins, the plugin falls back to direct Anthropic requests unless fallbackToDirect is set to false.
[!NOTE] The relay reduces upload bytes from your machine to Cloudflare. Anthropic still receives the complete
/v1/messagesrequest from the Worker, so the relay does not reduce Anthropic input tokens or billing by itself. Use prompt caching for server-side cache benefits.
Relay diagnostics
Relay diagnostics are written to a temp-file log:
tail -f "$(node -p 'require("node:os").tmpdir()')/opencode-anthropic-auth.log"Successful relay usage logs entries such as:
configured transport=websocket protocol=2
used relay transport=websocket protocol=2 mode=patch
used relay transport=http protocol=1 mode=full_syncDirect fallback logs falling back direct.
Request dumps
For relay/cache debugging, enable exact request dumps from inside OpenCode or Pi:
/claude-dump on
/claude-dump off
/claude-dumpWhen enabled, the plugin writes artifacts under the OS temp directory:
ls "$(node -p 'require("node:os").tmpdir()')/opencode-anthropic-auth-dumps"Each filename includes a sanitized session/affinity segment so dumps from different sessions are easier to find. Each request gets:
*.body.json— final rewritten Anthropic request body.*.meta.json— hashes, byte counts, diff ranges, model,messages[0]hash, later-message hash, and cache-relevant structure.*.relay.json— redacted relay payload/frame metadata for relay requests.*.request.json— redacted direct request URL, method, and headers for direct requests.
Dump state is persisted in the active sidecar config as dump.enabled (~/.config/opencode/anthropic-auth.json for OpenCode, ~/.pi/agent/anthropic-auth.json for Pi). Dumps may contain prompt content and should be treated as sensitive local debugging artifacts.
Environment variables
| Variable | Description |
| --- | --- |
| ANTHROPIC_BASE_URL | Override the Anthropic API endpoint. Must be HTTP(S). |
| ANTHROPIC_CUSTOM_HEADERS | Add proxy-specific headers on API-key and proxy routes. Authentication, protocol, framing, and internal correlation headers cannot be overridden. Ignored for OAuth requests. |
| ANTHROPIC_MODEL | Default proxy alias for any claude-* model. Ignored for OAuth requests. |
| ANTHROPIC_DEFAULT_SONNET_MODEL | Proxy alias for claude-sonnet-* models. |
| ANTHROPIC_DEFAULT_OPUS_MODEL | Proxy alias for claude-opus-* models. |
| ANTHROPIC_DEFAULT_HAIKU_MODEL | Proxy alias for claude-haiku-* models. |
| ANTHROPIC_DEFAULT_FABLE_MODEL | Proxy alias for claude-fable-* and claude-mythos-* models. |
| ANTHROPIC_INSECURE | Set to 1 or true to skip TLS verification when ANTHROPIC_BASE_URL is set. |
| OPENCODE_ANTHROPIC_AUTH_FILE | Override the OpenCode sidecar config path. |
| OPENCODE_ANTHROPIC_AUTH_CLAUSTRUM_ENROLLMENT_FILE | Override the owner-only OpenCode Claustrum enrollment-token path. |
| OPENCODE_ANTHROPIC_AUTH_FALLBACK_MODE | Set to legacy to bypass Anthropic's server policy and use deterministic 10-response client recovery exclusively. The default tries server-side safety fallback first and uses client recovery as a backstop. |
| PI_ANTHROPIC_AUTH_FILE | Override the Pi sidecar config path. |
| PI_AGENT_DIR | Override Pi's agent directory when deriving the default sidecar path. |
| CLOUDFLARE_API_TOKEN | Cloudflare token used by bunx @cortexkit/opencode-anthropic-auth relay setup. Not stored. |
| CLOUDFLARE_ACCOUNT_ID | Cloudflare account ID used by relay setup. |
ANTHROPIC_CUSTOM_HEADERS and the model-alias variables apply only to API-key and proxy routes. OAuth requests keep the Claude Code header and model identity unchanged. Custom headers accept either a JSON object or comma/newline-separated name: value entries. A configuration containing an invalid or protected header is ignored as a whole with one redacted warning; it cannot replace route authentication, Anthropic protocol headers, body framing, or plugin-internal correlation headers.
An ANTHROPIC_BASE_URL path is preserved. For example, https://proxy.example/anthropic sends requests to /anthropic/v1/messages, while a versioned base such as https://proxy.example/anthropic/v2 sends to /anthropic/v2/messages. Existing version segments are not duplicated, and unrelated paths ending in /messages are not rewritten.
Request rewriting
For Claude Pro/Max OAuth requests, the plugin works at the final Anthropic wire-request layer:
- Rewrites request URLs when
ANTHROPIC_BASE_URLis configured. - Normalizes Claude-compatible OAuth headers and beta flags.
- Removes OpenCode-specific identity text from system blocks.
- Prepends Claude Code identity and billing-header blocks.
- Rewrites cache controls according to
/claude-cachemode. - Renames MCP tool names into Claude-compatible PascalCase form.
- Opts eligible Fable 5/5.1 and Opus 5 OAuth requests into Anthropic's server-side safety fallback, restores stored fallback boundaries, and activates replay-safe client recovery if the response still refuses.
- Computes final-body
cchover the fully serialized request body.
The sanitizer is anchor-based: it removes paragraphs containing known OpenCode documentation or source anchors, performs a small set of inline replacements, and preserves the rest of the prompt including user/project instructions, tool policy, environment context, and file paths.
Development
Workspace layout:
packages/core Shared Anthropic auth core
packages/opencode OpenCode plugin and CLI
packages/pi Pi package/extensionInstall dependencies:
bun installRun checks:
bun run typecheck
bun run test
bun run build
bun run lint
bun run format:checkBuild modes
Two build modes are available:
bun run build # Deploy: bun build → bundled dist/ with all deps inlined (core + xxhash-wasm)
bun run build:dev # Dev: tsc → individual dist/*.js files (requires workspace node_modules)The default build uses bun build to bundle @cortexkit/anthropic-auth-core and all transitive dependencies into self-contained output files. No node_modules/ needed at runtime — the plugin works via file:// path in OpenCode config:
{
"plugin": ["file:///path/to/anthropic-auth/packages/opencode"]
}@opencode-ai/plugin remains external (peer dep provided by OpenCode).
Inspect package contents:
bun run pack:core:dry
bun run pack:opencode:dry
bun run pack:pi:dryTest a local build with OpenCode:
bun run devThis builds the plugin, symlinks the output into .opencode/plugins/, and starts tsc --watch. Restart OpenCode after starting the dev script and after rebuilds.
Clean the local dev symlink with:
bun run dev:cleanRelease
This repo uses CortexKit's tag-driven release workflow.
Preview a release:
./scripts/release.sh 1.9.0 --dryCreate and push the release tag:
./scripts/release.sh 1.9.0Wait for GitHub Actions:
./scripts/wait-release.sh v1.9.0The release workflow runs checks, publishes the core, OpenCode, and Pi packages to npm with provenance, and creates the GitHub release.
Troubleshooting
- Clear OpenCode's plugin cache after plugin config changes:
rm -rf ~/.cache/opencode. - Restart OpenCode or Pi after changing sidecar config; some settings are loaded at startup.
- If an OpenCode fallback account shows
invalid_grant, runbunx @cortexkit/opencode-anthropic-auth login <label>again for that account. - Tail relay diagnostics when debugging relay setup:
tail -f "$(node -p 'require("node:os").tmpdir()')/opencode-anthropic-auth.log". - Use
/claude-quotato inspect quota and refresh errors surfaced by the plugin.
License
MIT
