Cycle Anthropic OAuth keys with master API-key fallback.
Project description
tiny-claude-recycler
Rotate a pool of Claude OAuth subscription tokens. Fall back to a regular Anthropic API key when they're all rate-limited. Zero runtime dependencies.
from claude_agent_sdk import query
from tcr import recycler, Secret
recycler.master_key = Secret("sk-ant-api03-...")
recycler.oauth_keys = [Secret("sk-ant-oat01-..."), Secret("sk-ant-oat01-...")]
@recycler.cycle(retries=3)
def ask(prompt):
return query(prompt=prompt) # SDK reads env on this call, after our swap
That's it. Use claude_agent_sdk (or anthropic) as normal.
What it does on every call
- Sets
CLAUDE_CODE_OAUTH_TOKENto the next pool key. - Clears
ANTHROPIC_API_KEYandANTHROPIC_AUTH_TOKEN— both outrank OAuth in Claude Code's auth precedence and would silently override our token. - Runs your function.
- On exception → marks the key failed, advances the round-robin cursor, retries up to
retriestimes. - After
retriesfailures → swaps to master (ANTHROPIC_API_KEYset, OAuth cleared) and runs once more.
State (failures, cursor, cooldowns) is preserved on the module-level singleton, so the next call resumes where the last one left off. With 9 keys and retries=3 you naturally burn through them in batches of 3.
API
@recycler.cycle(
retries = 3, # OAuth attempts before master fallback
fallback_to_master = True, # False → re-raise the last OAuth error
cooldown_seconds = 60.0, # failed keys are skipped for this long
exceptions = (Exception,) # which exceptions trigger cycling
)
Works on def and async def. Inspect / reset:
recycler.state_snapshot() # {idx: {failures, last_error, cooldown_until, available, ...}}
recycler.reset_state()
Secret redacts its value in repr/str so tokens stay out of tracebacks and logs.
Production tips
Narrow the exception tuple. The default (Exception,) would burn keys on bugs. Use the curated helpers (lazy imports — only load if you call them):
from tcr import anthropic_exceptions, claude_agent_sdk_exceptions
@recycler.cycle(exceptions=anthropic_exceptions()) # 401/403/429/5xx/timeout/conn
def ask(prompt): ...
anthropic_exceptions() is verified against the SDK source: AuthenticationError, PermissionDeniedError, RateLimitError, OverloadedError, InternalServerError, ServiceUnavailableError, DeadlineExceededError, APIConnectionError, APITimeoutError.
Construct your Anthropic client inside the wrapped function (the import itself can be at module top — it's only the Anthropic() call that captures the env var). A long-lived client built before the decorator runs already pinned to whatever key was set at construction time and won't see swaps.
from anthropic import Anthropic # import: anywhere is fine
from tcr import recycler, anthropic_exceptions
@recycler.cycle(retries=3, exceptions=anthropic_exceptions())
def ask(prompt):
return Anthropic().messages.create( # construction: must be inside
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
For claude_agent_sdk, this caveat doesn't apply — every query(...) call spawns a fresh subprocess that reads env at spawn time.
Known sharp edges (it's a little sketchy, by design)
- Process-global env. Two decorated calls running concurrently across threads can race on
CLAUDE_CODE_OAUTH_TOKEN. Either serialize Claude calls, or run one event loop / thread. apiKeyHelperin~/.claude/settings.jsonoutranks OAuth and can't be cleared from env. If you use one, the recycler is a no-op forclaude_agent_sdk.- Bedrock/Vertex/Foundry flags route requests away from Anthropic entirely. Don't set those if you want the recycler to do anything.
- No proactive quota check. Anthropic doesn't expose subscription consumption via the API; this lib reacts to failures, it can't predict them.
Install
pip install -e ".[dev]"
pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tiny_claude_recycler-0.1.0.tar.gz.
File metadata
- Download URL: tiny_claude_recycler-0.1.0.tar.gz
- Upload date:
- Size: 10.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
33dfca32fc9d4939bf51f30b6291539cd292d3ba857471658dfd8847b0ed7973
|
|
| MD5 |
4bb34a885e59a17e1f1b63d61267f3b4
|
|
| BLAKE2b-256 |
828b2587714991cd751a0f89960d73317c498d924190409a26b82d52eff5e363
|
Provenance
The following attestation bundles were made for tiny_claude_recycler-0.1.0.tar.gz:
Publisher:
workflow.yml on lucastononro/tiny-claude-recycler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tiny_claude_recycler-0.1.0.tar.gz -
Subject digest:
33dfca32fc9d4939bf51f30b6291539cd292d3ba857471658dfd8847b0ed7973 - Sigstore transparency entry: 1541526088
- Sigstore integration time:
-
Permalink:
lucastononro/tiny-claude-recycler@2cecdddf84dd0f3265c41d38ced05252e5985cd2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/lucastononro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@2cecdddf84dd0f3265c41d38ced05252e5985cd2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file tiny_claude_recycler-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tiny_claude_recycler-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55c1b3be2a39565ca317049983cc06e02aee1ef82eb7c01332b565cc12d8bbe3
|
|
| MD5 |
2d22364c9014cb3a90554c147ebca9c0
|
|
| BLAKE2b-256 |
adec7a9ccd19c09263184afca740c3d89a2675223245f6d2223ce8167b570dc6
|
Provenance
The following attestation bundles were made for tiny_claude_recycler-0.1.0-py3-none-any.whl:
Publisher:
workflow.yml on lucastononro/tiny-claude-recycler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tiny_claude_recycler-0.1.0-py3-none-any.whl -
Subject digest:
55c1b3be2a39565ca317049983cc06e02aee1ef82eb7c01332b565cc12d8bbe3 - Sigstore transparency entry: 1541526171
- Sigstore integration time:
-
Permalink:
lucastononro/tiny-claude-recycler@2cecdddf84dd0f3265c41d38ced05252e5985cd2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/lucastononro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@2cecdddf84dd0f3265c41d38ced05252e5985cd2 -
Trigger Event:
release
-
Statement type: