Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3197,6 +3197,49 @@ describe("ClaudeAdapterV2 background wake turns", () => {
),
);

it.effect("shows a 403 permission error instead of the login hint", () =>
Effect.gen(function* () {
const harness = yield* makeWakeHarness;
const now = yield* DateTime.now;
yield* harness.runtime.startTurn(
makeClaudeTestTurnInput({
threadId: harness.threadId,
providerThread: harness.providerThread,
now,
attemptId: RunAttemptId.make("attempt-claude-permission-denied"),
text: "Continue.",
attachments: [],
}),
);
// Claude CLI 2.1.288 labels a 403 authentication_failed too.
const cliText =
"Failed to authenticate. API Error: 403 Your organization does not have access to this model.";
yield* Queue.offerAll(harness.sdkMessages, [
makeAssistantErrorFrame({
uuid: "00000000-0000-4000-8000-000000000608",
error: "authentication_failed",
}),
makeResultFrame({
uuid: "00000000-0000-4000-8000-000000000609",
result: cliText,
isError: true,
apiErrorStatus: 403,
terminalReason: "api_error",
}),
]);

const terminal = yield* Queue.take(harness.terminalReceipts);
assert.equal(terminal.status, "failed");
if (terminal.status !== "failed") return;
assert.equal(terminal.failure.message, cliText);
assert.equal(terminal.failure.code, "api_error_403");
}).pipe(
Effect.provide(
Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
),
),
);

it.effect.each([
{ recovered: false, expected: "Claude usage limit reached" },
{ recovered: true, expected: "Claude gave up after repeated API errors" },
Expand Down
15 changes: 10 additions & 5 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7676,11 +7676,16 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
(message.terminal_reason == null ||
message.terminal_reason === "api_error" ||
message.terminal_reason === "blocking_limit");
const failureHint =
context.authenticationFailureMessage ??
(usageLimited
? "Claude usage limit reached. Send the message again once the limit resets."
: undefined);
// The CLI also labels a 403 authentication_failed. Logging in again
// can't grant a permission, so show the CLI's own text instead.
const permissionDenied =
message.subtype === "success" && message.api_error_status === 403;
const failureHint = permissionDenied
? message.result
: (context.authenticationFailureMessage ??
(usageLimited
? "Claude usage limit reached. Send the message again once the limit resets."
: undefined));
const resetTimes = Array.from(context.rateLimitResetTimes.values());
const resetAt =
resetTimes.length > 0 && resetTimes.every((time) => time !== null)
Expand Down
Loading