Skip to content

Commit 8ad7bdc

Browse files
committed
fix: warn users when ANTHROPIC_API_KEY conflicts with Claude Code subscription
- Add environment variable detection in ClaudeCodeHandler constructor - Show warning notification when ANTHROPIC_API_KEY is detected - Add i18n translations for the warning message - Include "Learn More" action that links to Claude Code authentication docs - Add comprehensive tests for the new functionality Fixes #7196
1 parent b975ced commit 8ad7bdc

File tree

3 files changed

+180
-2
lines changed

3 files changed

+180
-2
lines changed

src/api/providers/__tests__/claude-code.spec.ts

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
1-
import { describe, test, expect, vi, beforeEach } from "vitest"
1+
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"
2+
import * as vscode from "vscode"
23
import { ClaudeCodeHandler } from "../claude-code"
34
import { ApiHandlerOptions } from "../../../shared/api"
45
import { ClaudeCodeMessage } from "../../../integrations/claude-code/types"
56

7+
// Mock vscode module
8+
vi.mock("vscode", () => ({
9+
window: {
10+
showWarningMessage: vi.fn(() => Promise.resolve(undefined)),
11+
},
12+
env: {
13+
openExternal: vi.fn(),
14+
},
15+
Uri: {
16+
parse: vi.fn((url: string) => ({ url })),
17+
},
18+
}))
19+
20+
// Mock i18n
21+
vi.mock("../../../i18n", () => ({
22+
t: vi.fn((key: string) => key),
23+
}))
24+
625
// Mock the runClaudeCode function
726
vi.mock("../../../integrations/claude-code/run", () => ({
827
runClaudeCode: vi.fn(),
@@ -20,16 +39,27 @@ const mockFilterMessages = vi.mocked(filterMessagesForClaudeCode)
2039

2140
describe("ClaudeCodeHandler", () => {
2241
let handler: ClaudeCodeHandler
42+
let originalEnv: NodeJS.ProcessEnv
2343

2444
beforeEach(() => {
2545
vi.clearAllMocks()
46+
// Save original environment
47+
originalEnv = { ...process.env }
48+
// Reset the static flag
49+
;(ClaudeCodeHandler as any).hasShownApiKeyWarning = false
50+
2651
const options: ApiHandlerOptions = {
2752
claudeCodePath: "claude",
2853
apiModelId: "claude-3-5-sonnet-20241022",
2954
}
3055
handler = new ClaudeCodeHandler(options)
3156
})
3257

58+
afterEach(() => {
59+
// Restore original environment
60+
process.env = originalEnv
61+
})
62+
3363
test("should create handler with correct model configuration", () => {
3464
const model = handler.getModel()
3565
expect(model.id).toBe("claude-3-5-sonnet-20241022")
@@ -563,4 +593,122 @@ describe("ClaudeCodeHandler", () => {
563593

564594
consoleSpy.mockRestore()
565595
})
596+
597+
describe("ANTHROPIC_API_KEY environment variable detection", () => {
598+
test("should show warning when ANTHROPIC_API_KEY is set", async () => {
599+
// Set the environment variable
600+
process.env.ANTHROPIC_API_KEY = "sk-ant-test-key"
601+
602+
// Reset the static flag to allow warning to show
603+
;(ClaudeCodeHandler as any).hasShownApiKeyWarning = false
604+
605+
// Create a new handler instance
606+
const options: ApiHandlerOptions = {
607+
claudeCodePath: "claude",
608+
apiModelId: "claude-3-5-sonnet-20241022",
609+
}
610+
new ClaudeCodeHandler(options)
611+
612+
// Verify warning was shown
613+
expect(vscode.window.showWarningMessage).toHaveBeenCalledWith(
614+
"common:warnings.anthropic_api_key_conflict",
615+
"common:actions.learn_more",
616+
)
617+
})
618+
619+
test("should not show warning when ANTHROPIC_API_KEY is not set", async () => {
620+
// Ensure the environment variable is not set
621+
delete process.env.ANTHROPIC_API_KEY
622+
623+
// Reset the static flag
624+
;(ClaudeCodeHandler as any).hasShownApiKeyWarning = false
625+
626+
// Create a new handler instance
627+
const options: ApiHandlerOptions = {
628+
claudeCodePath: "claude",
629+
apiModelId: "claude-3-5-sonnet-20241022",
630+
}
631+
new ClaudeCodeHandler(options)
632+
633+
// Verify warning was not shown
634+
expect(vscode.window.showWarningMessage).not.toHaveBeenCalled()
635+
})
636+
637+
test("should only show warning once per session", async () => {
638+
// Set the environment variable
639+
process.env.ANTHROPIC_API_KEY = "sk-ant-test-key"
640+
641+
// Reset the static flag
642+
;(ClaudeCodeHandler as any).hasShownApiKeyWarning = false
643+
644+
// Create first handler instance
645+
const options: ApiHandlerOptions = {
646+
claudeCodePath: "claude",
647+
apiModelId: "claude-3-5-sonnet-20241022",
648+
}
649+
new ClaudeCodeHandler(options)
650+
651+
// Clear mock calls
652+
vi.clearAllMocks()
653+
654+
// Create second handler instance
655+
new ClaudeCodeHandler(options)
656+
657+
// Verify warning was not shown the second time
658+
expect(vscode.window.showWarningMessage).not.toHaveBeenCalled()
659+
})
660+
661+
test("should open external link when Learn More is clicked", async () => {
662+
// Set the environment variable
663+
process.env.ANTHROPIC_API_KEY = "sk-ant-test-key"
664+
665+
// Reset the static flag
666+
;(ClaudeCodeHandler as any).hasShownApiKeyWarning = false
667+
668+
// Mock the showWarningMessage to simulate clicking "Learn More"
669+
vi.mocked(vscode.window.showWarningMessage).mockResolvedValue("common:actions.learn_more" as any)
670+
671+
// Create a new handler instance
672+
const options: ApiHandlerOptions = {
673+
claudeCodePath: "claude",
674+
apiModelId: "claude-3-5-sonnet-20241022",
675+
}
676+
new ClaudeCodeHandler(options)
677+
678+
// Wait for the promise to resolve
679+
await new Promise((resolve) => setTimeout(resolve, 0))
680+
681+
// Verify external link was opened
682+
expect(vscode.Uri.parse).toHaveBeenCalledWith(
683+
"https://docs.anthropic.com/en/docs/claude-code/setup#authentication",
684+
)
685+
expect(vscode.env.openExternal).toHaveBeenCalledWith({
686+
url: "https://docs.anthropic.com/en/docs/claude-code/setup#authentication",
687+
})
688+
})
689+
690+
test("should handle when user dismisses the warning", async () => {
691+
// Set the environment variable
692+
process.env.ANTHROPIC_API_KEY = "sk-ant-test-key"
693+
694+
// Reset the static flag
695+
;(ClaudeCodeHandler as any).hasShownApiKeyWarning = false
696+
697+
// Mock the showWarningMessage to simulate dismissing
698+
vi.mocked(vscode.window.showWarningMessage).mockResolvedValue(undefined as any)
699+
700+
// Create a new handler instance
701+
const options: ApiHandlerOptions = {
702+
claudeCodePath: "claude",
703+
apiModelId: "claude-3-5-sonnet-20241022",
704+
}
705+
new ClaudeCodeHandler(options)
706+
707+
// Wait for the promise to resolve
708+
await new Promise((resolve) => setTimeout(resolve, 0))
709+
710+
// Verify external link was not opened
711+
expect(vscode.env.openExternal).not.toHaveBeenCalled()
712+
})
713+
})
566714
})

src/api/providers/claude-code.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Anthropic } from "@anthropic-ai/sdk"
2+
import * as vscode from "vscode"
23
import {
34
claudeCodeDefaultModelId,
45
type ClaudeCodeModelId,
@@ -16,10 +17,35 @@ import { ApiHandlerOptions } from "../../shared/api"
1617

1718
export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
1819
private options: ApiHandlerOptions
20+
private static hasShownApiKeyWarning = false
1921

2022
constructor(options: ApiHandlerOptions) {
2123
super()
2224
this.options = options
25+
this.checkForAnthropicApiKey()
26+
}
27+
28+
private async checkForAnthropicApiKey() {
29+
// Only check and warn once per session to avoid annoying the user
30+
if (!ClaudeCodeHandler.hasShownApiKeyWarning && process.env.ANTHROPIC_API_KEY) {
31+
ClaudeCodeHandler.hasShownApiKeyWarning = true
32+
33+
try {
34+
// Show warning notification to the user
35+
const selection = await vscode.window.showWarningMessage(
36+
t("common:warnings.anthropic_api_key_conflict"),
37+
t("common:actions.learn_more"),
38+
)
39+
40+
if (selection === t("common:actions.learn_more")) {
41+
vscode.env.openExternal(
42+
vscode.Uri.parse("https://docs.anthropic.com/en/docs/claude-code/setup#authentication"),
43+
)
44+
}
45+
} catch {
46+
// Silently ignore any errors from the warning dialog
47+
}
48+
}
2349
}
2450

2551
override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {

src/i18n/locales/en/common.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@
105105
"warnings": {
106106
"no_terminal_content": "No terminal content selected",
107107
"missing_task_files": "This task's files are missing. Would you like to remove it from the task list?",
108-
"auto_import_failed": "Failed to auto-import RooCode settings: {{error}}"
108+
"auto_import_failed": "Failed to auto-import RooCode settings: {{error}}",
109+
"anthropic_api_key_conflict": "An ANTHROPIC_API_KEY environment variable was detected. This may conflict with your Claude Max/Pro subscription login and cause authentication errors. Please unset this environment variable to ensure your subscription plan is used correctly."
109110
},
110111
"info": {
111112
"no_changes": "No changes found.",
@@ -134,6 +135,9 @@
134135
"edit": "Edit",
135136
"learn_more": "Learn More"
136137
},
138+
"actions": {
139+
"learn_more": "Learn More"
140+
},
137141
"tasks": {
138142
"canceled": "Task error: It was stopped and canceled by the user.",
139143
"deleted": "Task failure: It was stopped and deleted by the user.",

0 commit comments

Comments
 (0)