docs(claude-code): mark subscription mode broken on macOS
PR #12 shipped oauthToken support with optimistic framing, but testing revealed @anthropic-ai/claude-agent-sdk has a macOS-specific bug: the SDK isolates CLAUDE_CONFIG_DIR per invocation and tries to copy ~/.claude/.credentials.json, which doesn't exist on macOS (creds live in the Keychain). The OAuth token gets misclassified as an API key and requests are billed against API credits instead of the subscription — subscribers without API credits see "Credit balance is too low" errors. Direct `claude -p` works fine, so the upstream SDK is the broken layer. Changes: - README Claude Code section: lead with the macOS status note and point subscribers at ClaudeProvider + ANTHROPIC_API_KEY as the current workaround. - JSDoc on ClaudeCodeProvider: same status note. - billing_error message: explain the bug and recommend the workaround so users can self-diagnose from the error alone. No code removal — when the SDK fix lands upstream, oauthToken users will Just Work without any code changes here.
This commit is contained in:
22
README.md
22
README.md
@@ -52,11 +52,15 @@ const claude = new ClaudeProvider({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Claude Code (subscription or API key via local CLI)
|
### Claude Code (via local CLI)
|
||||||
|
|
||||||
`ClaudeCodeProvider` wraps `@anthropic-ai/claude-agent-sdk`, which spawns the local `claude` CLI under the hood. It supports two billing modes:
|
`ClaudeCodeProvider` wraps `@anthropic-ai/claude-agent-sdk`, which spawns the local `claude` CLI under the hood.
|
||||||
|
|
||||||
**Mode A — Claude Pro/Max subscription** (billed against subscription credits):
|
> **Honest status:** the **API-key mode** (Mode B below) works reliably. The **subscription mode** (Mode A) is currently broken on macOS due to an upstream bug in `@anthropic-ai/claude-agent-sdk` — the SDK isolates `CLAUDE_CONFIG_DIR` per invocation and tries to copy a `~/.claude/.credentials.json` file that doesn't exist on macOS (Claude Code stores credentials in the Keychain). The provider misidentifies the OAuth token and bills it as an API key, so subscribers without API credits hit "Credit balance is too low".
|
||||||
|
>
|
||||||
|
> If you have a Pro/Max subscription and need programmatic access today: use the direct [`ClaudeProvider`](#claude-anthropic-api) with an API key, or wait for the upstream SDK fix. The `oauthToken` field is wired up and ready for when Anthropic fixes the Keychain handling.
|
||||||
|
|
||||||
|
**Mode A — Claude Pro/Max subscription** *(see status note above)*:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# One-time: install the CLI, then mint a long-lived OAuth token.
|
# One-time: install the CLI, then mint a long-lived OAuth token.
|
||||||
@@ -70,14 +74,9 @@ import { ClaudeCodeProvider } from 'simple-ai-provider';
|
|||||||
const claude = new ClaudeCodeProvider({
|
const claude = new ClaudeCodeProvider({
|
||||||
oauthToken: process.env.CLAUDE_CODE_OAUTH_TOKEN // from `claude setup-token`
|
oauthToken: process.env.CLAUDE_CODE_OAUTH_TOKEN // from `claude setup-token`
|
||||||
});
|
});
|
||||||
|
|
||||||
await claude.initialize();
|
|
||||||
const response = await claude.complete({
|
|
||||||
messages: [{ role: 'user', content: 'Hello!' }]
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Mode B — Console API key** (billed per-token):
|
**Mode B — Console API key** (billed per-token, works reliably):
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const claude = new ClaudeCodeProvider({
|
const claude = new ClaudeCodeProvider({
|
||||||
@@ -91,7 +90,7 @@ Optional config:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
new ClaudeCodeProvider({
|
new ClaudeCodeProvider({
|
||||||
oauthToken: '...',
|
apiKey: '...',
|
||||||
defaultModel: 'sonnet', // 'sonnet' | 'opus' | 'haiku' | 'inherit' | full model ID
|
defaultModel: 'sonnet', // 'sonnet' | 'opus' | 'haiku' | 'inherit' | full model ID
|
||||||
maxTurns: 1, // 1 for plain completion; raise for agent/tool loops
|
maxTurns: 1, // 1 for plain completion; raise for agent/tool loops
|
||||||
allowedTools: [], // tool names to enable (default: none)
|
allowedTools: [], // tool names to enable (default: none)
|
||||||
@@ -99,10 +98,11 @@ new ClaudeCodeProvider({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
**Trade-offs to know:**
|
**Trade-offs even when it works:**
|
||||||
- Requires `claude` CLI installed on the host. Not ideal for typical server deployments.
|
- Requires `claude` CLI installed on the host. Not ideal for typical server deployments.
|
||||||
- Higher latency than the direct API (spawns a CLI process per request).
|
- Higher latency than the direct API (spawns a CLI process per request).
|
||||||
- Streaming yields text as the SDK emits successive assistant messages, not token-by-token deltas.
|
- Streaming yields text as the SDK emits successive assistant messages, not token-by-token deltas.
|
||||||
|
- If you have an API key, [`ClaudeProvider`](#claude-anthropic-api) is the simpler, faster choice.
|
||||||
|
|
||||||
### OpenAI
|
### OpenAI
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,27 @@
|
|||||||
* Claude Code Provider
|
* Claude Code Provider
|
||||||
*
|
*
|
||||||
* Wraps `@anthropic-ai/claude-agent-sdk` so consumers can use a local
|
* Wraps `@anthropic-ai/claude-agent-sdk` so consumers can use a local
|
||||||
* Claude Code installation — including Claude Pro/Max subscription
|
* Claude Code installation through the same BaseAIProvider interface
|
||||||
* accounts authenticated via `claude login` — through the same
|
* as the other providers.
|
||||||
* BaseAIProvider interface as the other providers.
|
|
||||||
*
|
*
|
||||||
* Tradeoffs vs the direct Anthropic API provider:
|
* Status:
|
||||||
* - Authenticates via the local CLI, so subscription users (no API key)
|
* - apiKey mode: works reliably.
|
||||||
* can use it.
|
* - oauthToken (subscription) mode: currently broken on macOS due to an
|
||||||
* - Requires `claude` to be installed and logged in on the host.
|
* upstream SDK bug. The SDK isolates CLAUDE_CONFIG_DIR per invocation
|
||||||
* - Higher latency (shells out to a CLI process per request).
|
* and tries to copy ~/.claude/.credentials.json, which doesn't exist on
|
||||||
|
* macOS (creds live in the Keychain). The OAuth token gets misclassified
|
||||||
|
* as an API key by the SDK, so requests are billed against API credits
|
||||||
|
* instead of the subscription. Subscribers without API credits will see
|
||||||
|
* "Credit balance is too low" errors. The oauthToken field is wired up
|
||||||
|
* and will work as expected once the SDK fix lands upstream.
|
||||||
|
*
|
||||||
|
* Other tradeoffs:
|
||||||
|
* - Requires `claude` CLI installed on the host.
|
||||||
|
* - Higher latency (spawns a CLI process per request).
|
||||||
* - Designed for agent workflows, but used here in single-turn mode
|
* - Designed for agent workflows, but used here in single-turn mode
|
||||||
* (maxTurns: 1, no tools) for plain text completion.
|
* (maxTurns: 1, no tools) for plain text completion.
|
||||||
|
* - If you have an API key, prefer the direct ClaudeProvider for simpler,
|
||||||
|
* lower-latency access.
|
||||||
*
|
*
|
||||||
* @see https://docs.anthropic.com/claude/docs/claude-code-sdk
|
* @see https://docs.anthropic.com/claude/docs/claude-code-sdk
|
||||||
*/
|
*/
|
||||||
@@ -362,7 +372,7 @@ export class ClaudeCodeProvider extends BaseAIProvider {
|
|||||||
},
|
},
|
||||||
billing_error: {
|
billing_error: {
|
||||||
type: AIErrorType.AUTHENTICATION,
|
type: AIErrorType.AUTHENTICATION,
|
||||||
message: 'Billing error. For Claude Pro/Max subscribers using the SDK: run `claude setup-token` and pass the resulting token as `oauthToken` (interactive `claude login` alone is not sufficient for non-interactive SDK calls).'
|
message: 'Billing error from Claude Code SDK. If you are a Pro/Max subscriber on macOS, this is likely the upstream @anthropic-ai/claude-agent-sdk Keychain bug — the SDK misclassifies the OAuth token as an API key. Workaround: use ClaudeProvider directly with an ANTHROPIC_API_KEY instead.'
|
||||||
},
|
},
|
||||||
rate_limit: {
|
rate_limit: {
|
||||||
type: AIErrorType.RATE_LIMIT,
|
type: AIErrorType.RATE_LIMIT,
|
||||||
|
|||||||
Reference in New Issue
Block a user