Skip to content

Commit a03c002

Browse files
committed
fix: suppress verbose error for Claude Code 5-hour usage limit
- Detect 429 errors related to 5-hour usage limit in Claude Code provider - Suppress the verbose error details to avoid cluttering the UI - Keep only the concise grey notice for better user experience - Add comprehensive test coverage for various rate limit error messages Fixes #8128
1 parent 87b45de commit a03c002

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed

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

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,142 @@ describe("ClaudeCodeHandler", () => {
515515
await expect(iterator.next()).rejects.toThrow()
516516
})
517517

518+
test("should suppress verbose 5-hour usage limit errors", async () => {
519+
const systemPrompt = "You are a helpful assistant"
520+
const messages = [{ role: "user" as const, content: "Hello" }]
521+
522+
// Mock async generator that yields a 5-hour usage limit error
523+
const mockGenerator = async function* (): AsyncGenerator<ClaudeCodeMessage | string> {
524+
yield {
525+
type: "assistant" as const,
526+
message: {
527+
id: "msg_123",
528+
type: "message",
529+
role: "assistant",
530+
model: "claude-3-5-sonnet-20241022",
531+
content: [
532+
{
533+
type: "text",
534+
text: 'API Error: 429 {"error":{"message":"Usage limit reached. Please wait 5 hours before trying again."}}',
535+
},
536+
],
537+
stop_reason: "stop_sequence",
538+
stop_sequence: null,
539+
usage: {
540+
input_tokens: 10,
541+
output_tokens: 20,
542+
},
543+
} as any,
544+
session_id: "session_123",
545+
}
546+
}
547+
548+
mockRunClaudeCode.mockReturnValue(mockGenerator())
549+
550+
const stream = handler.createMessage(systemPrompt, messages)
551+
const results = []
552+
553+
// Should not throw an error - the error should be suppressed
554+
for await (const chunk of stream) {
555+
results.push(chunk)
556+
}
557+
558+
// Should have no results since the error was suppressed
559+
expect(results).toHaveLength(0)
560+
})
561+
562+
test("should suppress various 5-hour limit error messages", async () => {
563+
const systemPrompt = "You are a helpful assistant"
564+
const messages = [{ role: "user" as const, content: "Hello" }]
565+
566+
const errorMessages = [
567+
'API Error: 429 {"error":{"message":"5-hour usage limit exceeded"}}',
568+
'API Error: 429 {"error":{"message":"Five hour rate limit reached"}}',
569+
'API Error: 429 {"error":{"message":"Rate limit: Please wait before making another request"}}',
570+
'API Error: 429 {"error":{"message":"Usage limit has been reached for this period"}}',
571+
]
572+
573+
for (const errorMessage of errorMessages) {
574+
// Mock async generator that yields the error
575+
const mockGenerator = async function* (): AsyncGenerator<ClaudeCodeMessage | string> {
576+
yield {
577+
type: "assistant" as const,
578+
message: {
579+
id: "msg_123",
580+
type: "message",
581+
role: "assistant",
582+
model: "claude-3-5-sonnet-20241022",
583+
content: [
584+
{
585+
type: "text",
586+
text: errorMessage,
587+
},
588+
],
589+
stop_reason: "stop_sequence",
590+
stop_sequence: null,
591+
usage: {
592+
input_tokens: 10,
593+
output_tokens: 20,
594+
},
595+
} as any,
596+
session_id: "session_123",
597+
}
598+
}
599+
600+
mockRunClaudeCode.mockReturnValue(mockGenerator())
601+
602+
const stream = handler.createMessage(systemPrompt, messages)
603+
const results = []
604+
605+
// Should not throw an error - the error should be suppressed
606+
for await (const chunk of stream) {
607+
results.push(chunk)
608+
}
609+
610+
// Should have no results since the error was suppressed
611+
expect(results).toHaveLength(0)
612+
}
613+
})
614+
615+
test("should not suppress non-429 API errors", async () => {
616+
const systemPrompt = "You are a helpful assistant"
617+
const messages = [{ role: "user" as const, content: "Hello" }]
618+
619+
// Mock async generator that yields a non-429 error
620+
const mockGenerator = async function* (): AsyncGenerator<ClaudeCodeMessage | string> {
621+
yield {
622+
type: "assistant" as const,
623+
message: {
624+
id: "msg_123",
625+
type: "message",
626+
role: "assistant",
627+
model: "claude-3-5-sonnet-20241022",
628+
content: [
629+
{
630+
type: "text",
631+
text: 'API Error: 500 {"error":{"message":"Internal server error"}}',
632+
},
633+
],
634+
stop_reason: "stop_sequence",
635+
stop_sequence: null,
636+
usage: {
637+
input_tokens: 10,
638+
output_tokens: 20,
639+
},
640+
} as any,
641+
session_id: "session_123",
642+
}
643+
}
644+
645+
mockRunClaudeCode.mockReturnValue(mockGenerator())
646+
647+
const stream = handler.createMessage(systemPrompt, messages)
648+
const iterator = stream[Symbol.asyncIterator]()
649+
650+
// Should throw an error for non-429 errors
651+
await expect(iterator.next()).rejects.toThrow('{"error":{"message":"Internal server error"}}')
652+
})
653+
518654
test("should log warning for unsupported tool_use content", async () => {
519655
const systemPrompt = "You are a helpful assistant"
520656
const messages = [{ role: "user" as const, content: "Hello" }]

src/api/providers/claude-code.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
8585
throw new Error(content.text)
8686
}
8787

88+
// Check if this is a 5-hour usage limit error (429 status)
89+
// These errors should be handled gracefully without showing verbose details
90+
if (
91+
content.text.includes("429") &&
92+
(error.error?.message?.toLowerCase().includes("usage limit") ||
93+
error.error?.message?.toLowerCase().includes("rate limit") ||
94+
error.error?.message?.toLowerCase().includes("5-hour") ||
95+
error.error?.message?.toLowerCase().includes("five hour"))
96+
) {
97+
// Don't throw the verbose error - let the UI handle it with a concise message
98+
// The UI already shows a grey notice for this case
99+
return
100+
}
101+
88102
if (error.error.message.includes("Invalid model name")) {
89103
throw new Error(
90104
content.text + `\n\n${t("common:errors.claudeCode.apiKeyModelPlanMismatch")}`,

0 commit comments

Comments
 (0)