mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-16 08:25:14 +02:00
fix(cli): stop framing Claude Code session limits as auth failures (KLO-734) (#300)
The Claude Code auth probe wrapped every error as "authentication is not usable", so a subscription session limit told users to re-authenticate — which cannot help, since auth already succeeded and only a reset clears it. Discriminate probe failures with describeClaudeProbeFailure: session-limit and rate-limit hits get their own messages (preserving the upstream reset text), and genuine auth errors keep the original guidance. Also add the session/usage-limit markers to the shared rate-limit classifier so the governor stops treating a cap as a generic error. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7e29543398
commit
b81391cd9f
2 changed files with 87 additions and 2 deletions
|
|
@ -151,7 +151,26 @@ function expectedMcpServerNames(tools: KtxRuntimeToolSet | undefined): Set<strin
|
|||
return tools && Object.keys(tools).length > 0 ? new Set([KTX_MCP_SERVER_NAME]) : new Set();
|
||||
}
|
||||
|
||||
const CLAUDE_RATE_LIMIT_ERROR_MARKERS = /\b429\b|rate limit|too many requests|quota exceeded|overloaded|max_retries/i;
|
||||
// "session limit" is the Claude Code subscription cap ("You've hit your session
|
||||
// limit · resets …"); the rest are transient 429-style throttling. All mean
|
||||
// Claude Code authenticated successfully, so they must not be read as auth
|
||||
// failures by the governor classifier or the auth probe.
|
||||
const CLAUDE_RATE_LIMIT_ERROR_MARKERS =
|
||||
/\b429\b|rate limit|session limit|usage limit|too many requests|quota exceeded|overloaded|max_retries/i;
|
||||
|
||||
// The subscription cap is its own case: re-authenticating and retrying both fail
|
||||
// until reset, so it gets a distinct message from transient rate limiting.
|
||||
const CLAUDE_SESSION_LIMIT_MARKERS = /session limit|usage limit/i;
|
||||
|
||||
function describeClaudeProbeFailure(message: string): string {
|
||||
if (CLAUDE_SESSION_LIMIT_MARKERS.test(message)) {
|
||||
return `Claude Code session limit reached. Wait for the reset shown, then rerun setup or the command. Details: ${message}`;
|
||||
}
|
||||
if (CLAUDE_RATE_LIMIT_ERROR_MARKERS.test(message)) {
|
||||
return `Claude Code is rate limited. Retry shortly, then rerun setup or the command. Details: ${message}`;
|
||||
}
|
||||
return `Claude Code authentication is not usable. Authenticate Claude Code locally with the Claude Code CLI, then rerun setup or the command. ${message}`;
|
||||
}
|
||||
|
||||
function normalizeClaudeResetAtMs(value: unknown): number | undefined {
|
||||
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
||||
|
|
@ -497,7 +516,7 @@ export async function runClaudeCodeAuthProbe(input: {
|
|||
const message = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
ok: false,
|
||||
message: `Claude Code authentication is not usable. Authenticate Claude Code locally with the Claude Code CLI, then rerun setup or the command. ${message}`,
|
||||
message: describeClaudeProbeFailure(message),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -738,4 +738,70 @@ describe('ClaudeCodeKtxLlmRuntime', () => {
|
|||
message: 'Unsupported Claude Code model "gpt-5". Use sonnet, opus, haiku, or a claude-* model id.',
|
||||
});
|
||||
});
|
||||
|
||||
it('reports a Claude Code session limit as a session limit, not an auth or ktx failure', async () => {
|
||||
const query = vi.fn((_input: any) =>
|
||||
stream([
|
||||
initMessage(),
|
||||
resultMessage({
|
||||
subtype: 'error_during_execution',
|
||||
is_error: true,
|
||||
errors: ["You've hit your session limit · resets 10:50pm (Asia/Saigon)"],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await runClaudeCodeAuthProbe({ projectDir: '/tmp/project', model: 'sonnet', query, env: {} });
|
||||
const message = (result as { message: string }).message;
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
// Auth worked; the subscription is capped. Must not tell the user to re-authenticate.
|
||||
expect(message).not.toContain('authentication is not usable');
|
||||
// Frame it as Claude Code's session limit, tell them to wait, and preserve the reset text.
|
||||
expect(message).toContain('Claude Code session limit reached');
|
||||
expect(message).toContain('Wait for the reset shown');
|
||||
expect(message).toContain('resets 10:50pm (Asia/Saigon)');
|
||||
});
|
||||
|
||||
it('reports a Claude Code rate limit as a rate limit, not an auth failure', async () => {
|
||||
const query = vi.fn((_input: any) =>
|
||||
stream([
|
||||
initMessage(),
|
||||
resultMessage({
|
||||
subtype: 'error_during_execution',
|
||||
is_error: true,
|
||||
errors: ['API Error: 429 Too Many Requests'],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await runClaudeCodeAuthProbe({ projectDir: '/tmp/project', model: 'sonnet', query, env: {} });
|
||||
const message = (result as { message: string }).message;
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(message).not.toContain('authentication is not usable');
|
||||
expect(message).toContain('Claude Code is rate limited');
|
||||
expect(message).toContain('429 Too Many Requests');
|
||||
});
|
||||
|
||||
it('still reports a genuine auth failure as an auth failure', async () => {
|
||||
const query = vi.fn((_input: any) =>
|
||||
stream([
|
||||
initMessage(),
|
||||
resultMessage({
|
||||
subtype: 'error_during_execution',
|
||||
is_error: true,
|
||||
errors: ['Invalid API key · Please run `claude login`'],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await runClaudeCodeAuthProbe({ projectDir: '/tmp/project', model: 'sonnet', query, env: {} });
|
||||
const message = (result as { message: string }).message;
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(message).toContain('authentication is not usable');
|
||||
expect(message).not.toContain('session limit reached');
|
||||
expect(message).not.toContain('rate limited');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue