Description
Summary
@ai-sdk/anthropic reads ANTHROPIC_BASE_URL from the environment but does not normalize it — so the bare-host form (https://api.anthropic.com, which is what Claude Code and Cursor inject) silently produces 404 Not Found on every Anthropic call.
This is a silent, environment-specific breakage: the error message (AI_APICallError: Not Found) gives no hint that the issue is a missing /v1 in the base URL, so developers spend hours debugging.
Root Cause
In packages/anthropic/src/anthropic-provider.ts:
// Default baseURL includes /v1
baseURL: options.baseURL ?? process.env.ANTHROPIC_BASE_URL ?? 'https://api.anthropic.com/v1'
And in the request builder:
url: `${this.config.baseURL}/messages`
So the final URL is constructed by simple string concatenation. When ANTHROPIC_BASE_URL=https://api.anthropic.com (no /v1), the result is:
https://api.anthropic.com/messages ← 404
instead of the correct:
https://api.anthropic.com/v1/messages ← 200
Why This Matters — Claude Code / Cursor
Claude Code injects ANTHROPIC_BASE_URL=https://api.anthropic.com (bare host) into all child processes. This follows the official @anthropic-ai/sdk convention, which appends /v1 internally. Any app using @ai-sdk/anthropic inside Claude Code silently breaks the moment the env var is present.
Same behavior exists in:
- Cursor agent mode
- LiteLLM proxy setups that document the bare-host form
- Developers who configured their shell/CI following the official Anthropic SDK docs
Minimal Reproduction
ANTHROPIC_BASE_URL=https://api.anthropic.com \
ANTHROPIC_API_KEY=sk-ant-... \
node -e "
import('@ai-sdk/anthropic').then(async ({ anthropic }) => {
const { generateText } = await import('ai');
const result = await generateText({
model: anthropic('claude-opus-4-5-20250929'),
prompt: 'Say hello'
});
console.log(result.text);
})
"
Result: AI_APICallError: Not Found from https://api.anthropic.com/messages
Drop the env var → works fine.
Proposed Fix
Normalize baseURL at construction time in anthropic-provider.ts:
function normalizeAnthropicBaseURL(url: string): string {
// Accept both bare-host and /v1 forms; always return the /v1 form
const cleaned = url.replace(/\/+$/, '').replace(/\/v1$/, '');
return `${cleaned}/v1`;
}
const resolvedBaseURL = normalizeAnthropicBaseURL(
options.baseURL ?? process.env.ANTHROPIC_BASE_URL ?? 'https://api.anthropic.com'
);
This normalizes all variants correctly:
| Input |
Output |
Status |
https://api.anthropic.com |
https://api.anthropic.com/v1 |
✅ |
https://api.anthropic.com/v1 |
https://api.anthropic.com/v1 |
✅ |
https://api.anthropic.com/v1/ |
https://api.anthropic.com/v1 |
✅ |
https://my.proxy.com |
https://my.proxy.com/v1 |
✅ |
https://my.proxy.com/v1 |
https://my.proxy.com/v1 |
✅ |
Related
AI SDK Version
Reproduced on latest stable @ai-sdk/anthropic
Code of Conduct
Description
Summary
@ai-sdk/anthropicreadsANTHROPIC_BASE_URLfrom the environment but does not normalize it — so the bare-host form (https://api.anthropic.com, which is what Claude Code and Cursor inject) silently produces404 Not Foundon every Anthropic call.This is a silent, environment-specific breakage: the error message (
AI_APICallError: Not Found) gives no hint that the issue is a missing/v1in the base URL, so developers spend hours debugging.Root Cause
In
packages/anthropic/src/anthropic-provider.ts:And in the request builder:
url: `${this.config.baseURL}/messages`So the final URL is constructed by simple string concatenation. When
ANTHROPIC_BASE_URL=https://api.anthropic.com(no/v1), the result is:instead of the correct:
Why This Matters — Claude Code / Cursor
Claude Code injects
ANTHROPIC_BASE_URL=https://api.anthropic.com(bare host) into all child processes. This follows the official@anthropic-ai/sdkconvention, which appends/v1internally. Any app using@ai-sdk/anthropicinside Claude Code silently breaks the moment the env var is present.Same behavior exists in:
Minimal Reproduction
Result:
AI_APICallError: Not Foundfromhttps://api.anthropic.com/messagesDrop the env var → works fine.
Proposed Fix
Normalize
baseURLat construction time inanthropic-provider.ts:This normalizes all variants correctly:
https://api.anthropic.comhttps://api.anthropic.com/v1https://api.anthropic.com/v1https://api.anthropic.com/v1https://api.anthropic.com/v1/https://api.anthropic.com/v1https://my.proxy.comhttps://my.proxy.com/v1https://my.proxy.com/v1https://my.proxy.com/v1Related
@anthropic-ai/sdksource: appends/v1internally, uses bare host as defaultAI SDK Version
Reproduced on latest stable
@ai-sdk/anthropicCode of Conduct