Skip to content

Commit 0c385fd

Browse files
BunsDevCopilot
andauthored
Remove Claude auth token helper support (#450)
* Drop Claude auth token helper support - Require `claude auth login` for Claude setup - Remove helper-command settings, contracts, and picker logic - Update related error and setup copy * Remove marketing fog and simplify background (#451) - Replace layered atmospheric glows with cleaner grid and edge treatment - Remove unused section glow and reflection accents from hero/get-started * Require Claude CLI login and drop auth token helpers - Remove helper-command auth token resolution - Update Claude health checks and settings copy - Strip Anthropic credential env vars before launch * Update packages/contracts/src/orchestration.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 4e004fa commit 0c385fd

26 files changed

Lines changed: 72 additions & 544 deletions

apps/server/src/doctor.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ const doctorProgram = Effect.gen(function* () {
8787
console.log("No providers are ready. Set up at least one provider to start coding:");
8888
console.log("");
8989
console.log(" Codex: npm install -g @openai/codex && codex login");
90-
console.log(
91-
" Claude Code: npm install -g @anthropic-ai/claude-code && claude auth login (or set ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN)",
92-
);
90+
console.log(" Claude Code: npm install -g @anthropic-ai/claude-code && claude auth login");
9391
console.log(" Copilot: npm install -g @github/copilot && copilot login");
9492
} else if (readyCount === statuses.length) {
9593
console.log("All providers are ready.");

apps/server/src/provider/Layers/ClaudeAdapter.test.ts

Lines changed: 5 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ class FakeClaudeQuery implements AsyncIterable<SDKMessage> {
132132
function makeHarness(config?: {
133133
readonly nativeEventLogPath?: string;
134134
readonly nativeEventLogger?: ClaudeAdapterLiveOptions["nativeEventLogger"];
135-
readonly readAuthTokenFromHelperCommand?: ClaudeAdapterLiveOptions["readAuthTokenFromHelperCommand"];
136135
readonly cwd?: string;
137136
readonly baseDir?: string;
138137
}) {
@@ -149,11 +148,6 @@ function makeHarness(config?: {
149148
createInput = input;
150149
return query;
151150
},
152-
...(config?.readAuthTokenFromHelperCommand
153-
? {
154-
readAuthTokenFromHelperCommand: config.readAuthTokenFromHelperCommand,
155-
}
156-
: {}),
157151
...(config?.nativeEventLogger
158152
? {
159153
nativeEventLogger: config.nativeEventLogger,
@@ -358,64 +352,8 @@ describe("ClaudeAdapterLive", () => {
358352
);
359353
});
360354

361-
it.effect("sources ANTHROPIC_AUTH_TOKEN from the configured helper command", () => {
362-
const helperCommand = "op read op://shared/anthropic/token --no-newline";
363-
const helperToken = "helper-token";
364-
let helperCall: {
365-
readonly command: string;
366-
readonly options?: { readonly cwd?: string };
367-
} | null = null;
368-
const helper: NonNullable<ClaudeAdapterLiveOptions["readAuthTokenFromHelperCommand"]> = (
369-
command,
370-
options,
371-
) => {
372-
helperCall = {
373-
command,
374-
...(options?.cwd ? { options: { cwd: options.cwd } } : {}),
375-
};
376-
return helperToken;
377-
};
378-
const harness = makeHarness({
379-
readAuthTokenFromHelperCommand: helper,
380-
});
381-
382-
return Effect.gen(function* () {
383-
const adapter = yield* ClaudeAdapter;
384-
yield* adapter.startSession({
385-
threadId: THREAD_ID,
386-
provider: "claudeAgent",
387-
cwd: THREAD_CWD,
388-
runtimeMode: "full-access",
389-
env: {
390-
ANTHROPIC_AUTH_TOKEN: "",
391-
},
392-
providerOptions: {
393-
claudeAgent: {
394-
authTokenHelperCommand: helperCommand,
395-
},
396-
},
397-
});
398-
399-
const createInput = harness.getLastCreateQueryInput();
400-
assert.equal(createInput?.options.env?.ANTHROPIC_AUTH_TOKEN, helperToken);
401-
assert.equal(helperCall?.command, helperCommand);
402-
assert.equal(helperCall?.options?.cwd, THREAD_CWD);
403-
}).pipe(
404-
Effect.provideService(Random.Random, makeDeterministicRandomService()),
405-
Effect.provide(harness.layer),
406-
);
407-
});
408-
409-
it.effect("prefers an explicit ANTHROPIC_AUTH_TOKEN over the helper command", () => {
410-
let helperCallCount = 0;
411-
const helper: NonNullable<ClaudeAdapterLiveOptions["readAuthTokenFromHelperCommand"]> = () => {
412-
helperCallCount += 1;
413-
return "helper-token";
414-
};
415-
const harness = makeHarness({
416-
readAuthTokenFromHelperCommand: helper,
417-
});
418-
355+
it.effect("strips Anthropic credential env vars before starting Claude Code", () => {
356+
const harness = makeHarness();
419357
return Effect.gen(function* () {
420358
const adapter = yield* ClaudeAdapter;
421359
yield* adapter.startSession({
@@ -424,18 +362,14 @@ describe("ClaudeAdapterLive", () => {
424362
cwd: THREAD_CWD,
425363
runtimeMode: "full-access",
426364
env: {
365+
ANTHROPIC_API_KEY: "api-key",
427366
ANTHROPIC_AUTH_TOKEN: "env-token",
428367
},
429-
providerOptions: {
430-
claudeAgent: {
431-
authTokenHelperCommand: "op read op://shared/anthropic/token --no-newline",
432-
},
433-
},
434368
});
435369

436370
const createInput = harness.getLastCreateQueryInput();
437-
assert.equal(createInput?.options.env?.ANTHROPIC_AUTH_TOKEN, "env-token");
438-
assert.equal(helperCallCount, 0);
371+
assert.equal(createInput?.options.env?.ANTHROPIC_API_KEY, undefined);
372+
assert.equal(createInput?.options.env?.ANTHROPIC_AUTH_TOKEN, undefined);
439373
}).pipe(
440374
Effect.provideService(Random.Random, makeDeterministicRandomService()),
441375
Effect.provide(harness.layer),

apps/server/src/provider/Layers/ClaudeAdapter.ts

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ import {
7575
extractTextAttachmentContents,
7676
} from "../../attachmentText.ts";
7777
import { ServerConfig } from "../../config.ts";
78-
import { readClaudeAuthTokenFromHelperCommand } from "../claudeAuthTokenHelper.ts";
7978
import {
8079
ProviderAdapterProcessError,
8180
ProviderAdapterRequestError,
@@ -187,7 +186,6 @@ export interface ClaudeAdapterLiveOptions {
187186
readonly prompt: AsyncIterable<SDKUserMessage>;
188187
readonly options: ClaudeQueryOptions;
189188
}) => ClaudeQueryRuntime;
190-
readonly readAuthTokenFromHelperCommand?: typeof readClaudeAuthTokenFromHelperCommand;
191189
readonly nativeEventLogPath?: string;
192190
readonly nativeEventLogger?: EventNdjsonLogger;
193191
}
@@ -211,12 +209,6 @@ function toError(cause: unknown, fallback: string): Error {
211209
return cause instanceof Error ? cause : new Error(toMessage(cause, fallback));
212210
}
213211

214-
function nonEmptyTrimmed(value: string | undefined): string | undefined {
215-
if (!value) return undefined;
216-
const trimmed = value.trim();
217-
return trimmed.length > 0 ? trimmed : undefined;
218-
}
219-
220212
function normalizeClaudeStreamMessages(cause: Cause.Cause<Error>): ReadonlyArray<string> {
221213
const errors = Cause.prettyErrors(cause)
222214
.map((error) => error.message.trim())
@@ -263,7 +255,7 @@ function isClaudeAuthCause(cause: Cause.Cause<Error>): boolean {
263255
}
264256

265257
function claudeAuthFailureMessage(): string {
266-
return "Claude Code is not configured with a supported Anthropic credential. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN and try again.";
258+
return "Claude Code must be authenticated with `claude auth login` before starting a session. API key and auth token credentials are not supported.";
267259
}
268260

269261
function messageFromClaudeStreamCause(cause: Cause.Cause<Error>, fallback: string): string {
@@ -1025,8 +1017,6 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
10251017
stream: "native",
10261018
})
10271019
: undefined);
1028-
const readAuthTokenFromHelperCommand =
1029-
options?.readAuthTokenFromHelperCommand ?? readClaudeAuthTokenFromHelperCommand;
10301020

10311021
const createQuery =
10321022
options?.createQuery ??
@@ -2832,28 +2822,11 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
28322822
};
28332823
const runtimeEnv = input.env ? compactNodeProcessEnv(input.env) : undefined;
28342824
const baseEnv = mergeNodeProcessEnv(process.env, runtimeEnv);
2835-
const explicitAuthToken = nonEmptyTrimmed(baseEnv.ANTHROPIC_AUTH_TOKEN);
2836-
const helperCommand = providerOptions?.authTokenHelperCommand;
2837-
let authToken = explicitAuthToken;
2838-
if (!authToken && helperCommand) {
2839-
authToken = yield* Effect.try({
2840-
try: () =>
2841-
readAuthTokenFromHelperCommand(helperCommand, {
2842-
...(input.cwd ? { cwd: input.cwd } : {}),
2843-
env: baseEnv,
2844-
}),
2845-
catch: (cause) =>
2846-
new ProviderAdapterProcessError({
2847-
provider: PROVIDER,
2848-
threadId,
2849-
detail: `Failed to resolve Claude auth token from helper command: ${toMessage(cause, "unknown error")}`,
2850-
cause,
2851-
}),
2852-
});
2853-
}
2854-
const queryEnv = authToken
2855-
? mergeNodeProcessEnv(baseEnv, { ANTHROPIC_AUTH_TOKEN: authToken })
2856-
: baseEnv;
2825+
const {
2826+
ANTHROPIC_API_KEY: _anthropicApiKey,
2827+
ANTHROPIC_AUTH_TOKEN: _anthropicAuthToken,
2828+
...queryEnv
2829+
} = baseEnv;
28572830

28582831
const queryOptions: ClaudeQueryOptions = {
28592832
...(input.cwd ? { cwd: input.cwd } : {}),

apps/server/src/provider/Layers/ProviderHealth.test.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -470,13 +470,17 @@ it.layer(NodeServices.layer)("ProviderHealth", (it) => {
470470
// ── checkClaudeProviderStatus tests ──────────────────────────
471471

472472
describe("checkClaudeProviderStatus", () => {
473-
it.effect("returns ready when claude is installed and authenticated", () =>
473+
it.effect("rejects Claude API-key auth and requires CLI login", () =>
474474
Effect.gen(function* () {
475475
const status = yield* checkClaudeProviderStatus;
476476
assert.strictEqual(status.provider, "claudeAgent");
477-
assert.strictEqual(status.status, "ready");
477+
assert.strictEqual(status.status, "error");
478478
assert.strictEqual(status.available, true);
479-
assert.strictEqual(status.authStatus, "authenticated");
479+
assert.strictEqual(status.authStatus, "unauthenticated");
480+
assert.strictEqual(
481+
status.message,
482+
"Claude authentication status reported unsupported credential type 'apiKey'. Run `claude auth login` and try again. API key and auth token credentials are not supported.",
483+
);
480484
}).pipe(
481485
Effect.provide(
482486
mockSpawnerLayer((args) => {
@@ -535,7 +539,7 @@ it.layer(NodeServices.layer)("ProviderHealth", (it) => {
535539
assert.strictEqual(status.authStatus, "unauthenticated");
536540
assert.strictEqual(
537541
status.message,
538-
"Claude is not configured with a supported Anthropic credential. Run `claude auth login`, or set ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN, and try again.",
542+
"Claude Code must be authenticated with `claude auth login` before starting a session. Run `claude auth login` and try again. API key and auth token credentials are not supported.",
539543
);
540544
}).pipe(
541545
Effect.provide(
@@ -561,7 +565,7 @@ it.layer(NodeServices.layer)("ProviderHealth", (it) => {
561565
assert.strictEqual(status.status, "ready");
562566
assert.strictEqual(status.available, true);
563567
assert.strictEqual(status.authStatus, "authenticated");
564-
assert.strictEqual(status.message, "Claude Code CLI is ready via Claude.ai login.");
568+
assert.strictEqual(status.message, "Claude Code CLI is ready via `claude auth login`.");
565569
}).pipe(
566570
Effect.provide(
567571
mockSpawnerLayer((args) => {
@@ -626,10 +630,10 @@ it.layer(NodeServices.layer)("ProviderHealth", (it) => {
626630
// ── parseClaudeAuthStatusFromOutput pure tests ────────────────────
627631

628632
describe("parseClaudeAuthStatusFromOutput", () => {
629-
it("exit code 0 with no auth markers is ready", () => {
633+
it("exit code 0 with no auth markers is unknown", () => {
630634
const parsed = parseClaudeAuthStatusFromOutput({ stdout: "OK\n", stderr: "", code: 0 });
631-
assert.strictEqual(parsed.status, "ready");
632-
assert.strictEqual(parsed.authStatus, "authenticated");
635+
assert.strictEqual(parsed.status, "warning");
636+
assert.strictEqual(parsed.authStatus, "unknown");
633637
});
634638

635639
it("JSON with loggedIn=true is authenticated", () => {
@@ -640,7 +644,7 @@ it.layer(NodeServices.layer)("ProviderHealth", (it) => {
640644
});
641645
assert.strictEqual(parsed.status, "ready");
642646
assert.strictEqual(parsed.authStatus, "authenticated");
643-
assert.strictEqual(parsed.message, "Claude Code CLI is ready via Claude.ai login.");
647+
assert.strictEqual(parsed.message, "Claude Code CLI is ready via `claude auth login`.");
644648
});
645649

646650
it("JSON with loggedIn=false is unauthenticated", () => {

apps/server/src/provider/Layers/ProviderHealth.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,8 @@ function hasGeminiHeadlessAuthEnv(): boolean {
160160
}
161161

162162
const CLAUDE_CLI_AUTH_METHODS = new Set(["claude.ai", "oauth"]);
163-
const CLAUDE_SUPPORTED_AUTH_METHODS = new Set(["apiKey", "authToken", "claude.ai", "oauth"]);
164163
const CLAUDE_AUTH_GUIDANCE =
165-
"Run `claude auth login`, or set ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN, and try again.";
164+
"Run `claude auth login` and try again. API key and auth token credentials are not supported.";
166165

167166
export function parseAuthStatusFromOutput(result: CommandResult): {
168167
readonly status: ServerProviderStatusState;
@@ -660,14 +659,14 @@ export function parseClaudeAuthStatusFromOutput(result: CommandResult): {
660659
lowerOutput.includes("not logged in") ||
661660
lowerOutput.includes("login required") ||
662661
lowerOutput.includes("authentication required") ||
663-
lowerOutput.includes("oauth authentication is currently not supported") ||
664662
lowerOutput.includes("run `claude login`") ||
665-
lowerOutput.includes("run claude login")
663+
lowerOutput.includes("run claude login") ||
664+
lowerOutput.includes("api key and auth token credentials are not supported")
666665
) {
667666
return {
668667
status: "error",
669668
authStatus: "unauthenticated",
670-
message: `Claude is not configured with a supported Anthropic credential. ${CLAUDE_AUTH_GUIDANCE}`,
669+
message: `Claude Code must be authenticated with \`claude auth login\` before starting a session. ${CLAUDE_AUTH_GUIDANCE}`,
671670
};
672671
}
673672

@@ -700,35 +699,39 @@ export function parseClaudeAuthStatusFromOutput(result: CommandResult): {
700699
const authMethod = parsedAuth.authMethod?.trim();
701700
const normalizedAuthMethod = authMethod?.toLowerCase();
702701
if (parsedAuth.auth === true) {
703-
if (authMethod && !CLAUDE_SUPPORTED_AUTH_METHODS.has(authMethod)) {
702+
if (normalizedAuthMethod && !CLAUDE_CLI_AUTH_METHODS.has(normalizedAuthMethod)) {
704703
return {
705-
status: "warning",
706-
authStatus: "unknown",
707-
message: `Claude authentication status reported an unsupported credential type '${authMethod}'. ${CLAUDE_AUTH_GUIDANCE}`,
704+
status: "error",
705+
authStatus: "unauthenticated",
706+
message: `Claude authentication status reported unsupported credential type '${authMethod}'. ${CLAUDE_AUTH_GUIDANCE}`,
708707
};
709708
}
710709
if (normalizedAuthMethod && CLAUDE_CLI_AUTH_METHODS.has(normalizedAuthMethod)) {
711710
return {
712711
status: "ready",
713712
authStatus: "authenticated",
714-
message: "Claude Code CLI is ready via Claude.ai login.",
713+
message: "Claude Code CLI is ready via `claude auth login`.",
715714
};
716715
}
717-
return { status: "ready", authStatus: "authenticated" };
716+
return {
717+
status: "warning",
718+
authStatus: "unknown",
719+
message: "Could not verify Claude CLI authentication method from status output.",
720+
};
718721
}
719722
if (parsedAuth.auth === false) {
720723
return {
721724
status: "error",
722725
authStatus: "unauthenticated",
723-
message: `Claude is not configured with a supported Anthropic credential. ${CLAUDE_AUTH_GUIDANCE}`,
726+
message: `Claude Code must be authenticated with \`claude auth login\` before starting a session. ${CLAUDE_AUTH_GUIDANCE}`,
724727
};
725728
}
726729
if (parsedAuth.attemptedJsonParse) {
727-
if (authMethod && !CLAUDE_SUPPORTED_AUTH_METHODS.has(authMethod)) {
730+
if (normalizedAuthMethod && !CLAUDE_CLI_AUTH_METHODS.has(normalizedAuthMethod)) {
728731
return {
729732
status: "error",
730733
authStatus: "unauthenticated",
731-
message: `Claude authentication status reported an unsupported credential type '${authMethod}'. ${CLAUDE_AUTH_GUIDANCE}`,
734+
message: `Claude authentication status reported unsupported credential type '${authMethod}'. ${CLAUDE_AUTH_GUIDANCE}`,
732735
};
733736
}
734737
return {
@@ -738,10 +741,6 @@ export function parseClaudeAuthStatusFromOutput(result: CommandResult): {
738741
"Could not verify Claude authentication status from JSON output (missing auth marker).",
739742
};
740743
}
741-
if (result.code === 0) {
742-
return { status: "ready", authStatus: "authenticated" };
743-
}
744-
745744
const detail = detailFromResult(result);
746745
return {
747746
status: "warning",

0 commit comments

Comments
 (0)