opencode-anthropic-auth-loocos
v1.13.0
Published
An [OpenCode](https://github.com/anomalyco/opencode) plugin that provides Anthropic OAuth authentication, enabling Claude Pro/Max users to use their subscription directly with OpenCode.
Downloads
284
Readme
OpenCode Anthropic Auth Plugin
An OpenCode plugin that provides Anthropic OAuth authentication, enabling Claude Pro/Max users to use their subscription directly with OpenCode.
[!NOTE] This package (
opencode-anthropic-auth-loocos) is a fork of the upstream@ex-machina/opencode-anthropic-auththat adds multiple-account support with automatic failover. It is published independently and is not affiliated with the upstream maintainers.
[!IMPORTANT] If you are seeing issues, please try to
rm -rf ~/.cache/opencodeand check youropencode.jsonconfig to make sure you're on the latest version.Try this FIRST before making an Issue. Thanks!
Usage
Add the plugin to your OpenCode configuration:
{
"plugin": ["opencode-anthropic-auth-loocos"]
}[!TIP] It is STRONGLY advised that you pin the plugin to a version. This will keep you from getting automatic updates; however, this will protect you from nefarious updates.
This holds true for ANY OpenCode plugin. If you do not pin them, OpenCode will automatically update them on startup. It's a massive vulnerability waiting to happen.
Example of pinned version
{
"plugin": ["[email protected]"]
}Authentication Methods
The plugin provides three authentication options:
- Claude Pro/Max - OAuth flow via
claude.aifor Pro/Max subscribers. Uses your existing subscription at no additional API cost. - Create an API Key - OAuth flow via
console.anthropic.comthat creates an API key on your behalf. - Manually enter API Key - Standard API key entry for users who already have one.
Multiple Accounts & Automatic Failover
You can authenticate multiple Claude Pro/Max accounts and have the plugin automatically switch between them when one runs out of usage.
Adding accounts
There is no separate "add account" option — failover is transparent. Just sign in with Claude Pro/Max as many times as you like:
- Sign in via Claude Pro/Max (this becomes your primary account).
- To add more accounts, run Claude Pro/Max again and log in with a different Claude account. Each login is saved to the account pool (labeled by email) and becomes the current primary. Repeat for as many accounts as you want.
[!IMPORTANT] Failover only helps across distinct Claude accounts — multiple logins of the same account share one usage quota. The plugin deduplicates by email, so re-logging into an account you've already added just refreshes that entry rather than creating a useless duplicate. Make sure you log in with genuinely different Claude accounts.
How failover works
On every request the plugin builds an ordered list of candidate accounts and tries them in turn:
- Your primary (OpenCode) account first.
- Then each additional account that isn't currently cooling down.
- Then, as a last resort, accounts that are cooling down — so the session never stalls waiting for a human while an account might still work.
Failover triggers when a request fails in any of these ways:
- An HTTP error status:
429(rate limit),401/403(auth), or529(overloaded). - A token refresh failure such as
invalid_grant(a logged-out / rotated refresh token). This is treated as that account being unavailable — the plugin moves on to the next account instead of surfacing the error. - An error inside a
200 OKstreaming response. Claude Pro/Max usage limits frequently come back as HTTP 200 with anerrorevent in the SSE stream (e.g.rate_limit_error), not as a429. The plugin inspects the stream up to the point real content begins — so even an error that arrives a few events in (aftermessage_start) triggers failover. This is the common "my account got restricted mid-session" scenario.
The exhausted account is put on a cooldown (respecting any Retry-After
header) and the same request is transparently retried on the next account. An
error is only surfaced to you once every account has failed.
Self-healing primary: when a non-primary account serves a request because the primary failed (e.g. the primary's refresh token died), that working account is promoted into OpenCode's own credential slot. Subsequent requests then start from a healthy account instead of retrying the dead primary every time.
Access tokens are refreshed per account, and rotated refresh tokens are persisted automatically. Cooldowns expire on their own, so an account becomes available again after its rate-limit window passes. An account whose refresh token is permanently invalid (e.g. logged out elsewhere) is cooled down for an hour so it stops being retried on every request.
Account labels (email)
Each account is labeled with its Claude email address so you can tell them
apart. The email is captured from the OAuth token response at login, or looked
up from Anthropic's profile endpoint (GET /api/oauth/profile, the
user:profile scope) when the token response doesn't include it. Accounts that
predate this feature are labeled automatically the first time they successfully
serve a request. The email is stored as email/label in accounts.json and
shown in the debug logs below.
Debugging failover
Set ANTHROPIC_FAILOVER_DEBUG=1 to log candidate selection and failover
decisions to stderr, e.g.:
[anthropic-failover] request: 3 candidate account(s) [ 'primary', '[email protected]', '[email protected]' ]
[anthropic-failover] account primary: stream error → failover
[anthropic-failover] account [email protected]: OK → promoted to primaryWhere accounts are stored
Accounts are stored in a plugin-owned JSON file (mode 0600):
$ANTHROPIC_ACCOUNTS_PATHif set, otherwise$XDG_DATA_HOME/opencode-anthropic-auth/accounts.json, otherwise~/.local/share/opencode-anthropic-auth/accounts.json
Each entry records the account's email (used as its label), its OAuth
tokens, any cooldownUntil timestamp, and a primary: true flag on the single
account currently held in OpenCode's credential slot (the one tried first). For
example:
{
"version": 1,
"accounts": [
{ "id": "…", "label": "[email protected]", "email": "[email protected]", "primary": true, "refresh": "…", "access": "…", "expires": 1788… },
{ "id": "…", "label": "[email protected]", "email": "[email protected]", "refresh": "…", "access": "…", "expires": 1788…, "cooldownUntil": 1788… }
]
}To remove an account, delete its entry from that file (or delete the file to
reset the pool). The account marked primary mirrors OpenCode's own credential
store and updates automatically as accounts rotate.
Configuration
The plugin supports the following environment variables:
| Variable | Description |
|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ANTHROPIC_BASE_URL | Override the API endpoint URL (e.g. for proxying). Must be a valid HTTP(S) URL. |
| ANTHROPIC_INSECURE | Set to 1 or true to skip TLS certificate verification. Only effective when ANTHROPIC_BASE_URL is also set. |
| ANTHROPIC_ACCOUNTS_PATH | Override the path to the multi-account store file. Defaults to $XDG_DATA_HOME/opencode-anthropic-auth/accounts.json (or ~/.local/share/...). |
| ANTHROPIC_FAILOVER_DEBUG | Set to 1 or true to log multi-account candidate selection and failover decisions to stderr. |
How It Works
For Claude Pro/Max authentication, the plugin:
- Initiates a PKCE OAuth flow against Anthropic's authorization endpoint
- Exchanges the authorization code for access and refresh tokens
- Automatically refreshes expired tokens (per account)
- Injects the required OAuth headers and beta flags into API requests
- Sanitizes the system prompt for compatibility (see below)
- Zeros out model costs (since usage is covered by the subscription)
- Automatically fails over to another authenticated account when one is rate-limited or exhausted (see Multiple Accounts & Automatic Failover)
System Prompt Sanitization
The Anthropic API for Max subscriptions has specific requirements for the system prompt to identify as Claude Code. The plugin rewrites the system prompt on each request using an anchor-based approach that minimizes what gets changed:
- Identity swap — The OpenCode identity line is removed and replaced with the Claude Code identity.
- Paragraph removal by anchor — Any paragraph containing a known URL anchor (e.g.
github.com/anomalyco/opencode,opencode.ai/docs) is removed entirely. This is resilient to upstream rewording — as long as the anchor URL appears somewhere in the paragraph, the removal works regardless of surrounding text changes. - Inline text replacements — Short branded strings inside paragraphs we want to keep are replaced (e.g. "OpenCode" → "the assistant" in the professional objectivity section).
Everything else in the system prompt is preserved: tone/style guidance, task management instructions, tool usage policy, environment info, skills, user/project instructions, and file paths containing "opencode". The sanitized system prompt is structured as three blocks in system[]: the billing header, the Claude Code identity line, and the remaining system content.
Development
Local Testing
Use bun run dev to test plugin changes locally without publishing to npm:
bun run devThis does three things:
- Builds the plugin
- Symlinks the build output into
.opencode/plugins/so OpenCode loads it as a local plugin - Starts
tsc --watchfor automatic rebuilds on source changes
After starting the dev script, restart OpenCode in this project directory to pick up the local build. Any edits to src/ will trigger a rebuild — restart OpenCode again to load the new version.
Ctrl+C stops the watcher and cleans up the symlink. If the process was killed without cleanup (e.g. kill -9), you can manually remove the symlink:
bun run dev:clean[!NOTE] If you have the npm version of this plugin in your global OpenCode config, both will load. The local version takes precedence for auth handling.
Publishing
This project uses changesets for versioning and publishing. See the changeset README for more details.
bun change # create a changeset describing your changesWhen changesets are merged to main, CI will automatically open a release PR. Merging that PR publishes to npm.
License
MIT
