f4788e4af8
* fix(auth): detect auth errors returned as AI response text and prevent retry loops Auth errors like "Your account does not have access to Claude" were returned as conversational AI text rather than HTTP errors, causing process_sdk_stream to loop ~500 times until the circuit breaker killed the session. This adds detection at three layers: - sdk_utils: _is_auth_error_response() catches auth errors in AI text blocks and breaks the stream immediately; repeated identical response detection aborts after 3 consecutive repeats - error_utils: "does not have access to claude" and "please login again" patterns added to is_authentication_error() - rate-limit-detector: matching regex patterns added to AUTH_FAILURE_PATTERNS for Electron-side subprocess monitoring Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(auth): prevent false positive auth modal from AI response text The previous commit (825c6217) added broad auth detection patterns that match on normal AI discussion text — e.g., a PR review agent discussing authentication would trigger the auth failure modal incorrectly. Frontend: Remove two overly broad regex patterns from AUTH_FAILURE_PATTERNS ("does not have access to Claude", "please login again"). Real auth errors are already caught by the remaining 11 structured patterns (JSON types, HTTP status codes, CLI bracket-prefixed messages, Error: prefix). Backend: Add MAX_AUTH_ERROR_LENGTH (300) guard to _is_auth_error_response() so long AI discussion text mentioning auth topics is not flagged. Real API auth error messages are consistently under 100 chars. Tests: Replace removed positive-match tests with false-positive regression test. Add backend boundary tests at exactly 300/301 chars. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(auth): address PR review findings in sdk_utils - Remove redundant "does not have access to claude" pattern since "not have access to claude" already subsumes it as a substring - Wrap repeated-response tracking in `if _stripped:` so empty text blocks don't reset the counter (prevents theoretical loop evasion) - Add clarifying comment that auth error break exits inner for-loop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(auth): remove overly broad access pattern and lower repeat threshold Remove "account does not have access" from _is_auth_error_response() as it could false-positive on short AI responses about general access control. Lower REPEATED_RESPONSE_THRESHOLD from 3 to 1 so error loops (including auth errors returned as AI text) are caught after just 2 identical messages, making broad content matching unnecessary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
121 lines
3.8 KiB
Python
121 lines
3.8 KiB
Python
"""
|
|
Shared Error Utilities
|
|
======================
|
|
|
|
Common error detection and classification functions used across
|
|
agent sessions, QA, and other modules.
|
|
"""
|
|
|
|
import re
|
|
|
|
|
|
def is_tool_concurrency_error(error: Exception) -> bool:
|
|
"""
|
|
Check if an error is a 400 tool concurrency error from Claude API.
|
|
|
|
Tool concurrency errors occur when too many tools are used simultaneously
|
|
in a single API request, hitting Claude's concurrent tool use limit.
|
|
|
|
Args:
|
|
error: The exception to check
|
|
|
|
Returns:
|
|
True if this is a tool concurrency error, False otherwise
|
|
"""
|
|
error_str = str(error).lower()
|
|
# Check for 400 status AND tool concurrency keywords
|
|
return "400" in error_str and (
|
|
("tool" in error_str and "concurrency" in error_str)
|
|
or "too many tools" in error_str
|
|
or "concurrent tool" in error_str
|
|
)
|
|
|
|
|
|
def is_rate_limit_error(error: Exception) -> bool:
|
|
"""
|
|
Check if an error is a rate limit error (429 or similar).
|
|
|
|
Rate limit errors occur when the API usage quota is exceeded,
|
|
either for session limits or weekly limits.
|
|
|
|
Args:
|
|
error: The exception to check
|
|
|
|
Returns:
|
|
True if this is a rate limit error, False otherwise
|
|
"""
|
|
error_str = str(error).lower()
|
|
|
|
# Check for HTTP 429 with word boundaries to avoid false positives
|
|
if re.search(r"\b429\b", error_str):
|
|
return True
|
|
|
|
# Check for other rate limit indicators
|
|
return any(
|
|
p in error_str
|
|
for p in [
|
|
"limit reached",
|
|
"rate limit",
|
|
"too many requests",
|
|
"usage limit",
|
|
"quota exceeded",
|
|
]
|
|
)
|
|
|
|
|
|
def is_authentication_error(error: Exception) -> bool:
|
|
"""
|
|
Check if an error is an authentication error (401, token expired, etc.).
|
|
|
|
Authentication errors occur when OAuth tokens are invalid, expired,
|
|
or have been revoked (e.g., after token refresh on another process).
|
|
|
|
Validation approach:
|
|
- HTTP 401 status code is checked with word boundaries to minimize false positives
|
|
- Additional string patterns are validated against lowercase error messages
|
|
- Patterns are designed to match known Claude API and OAuth error formats
|
|
|
|
Known false positive risks:
|
|
- Generic error messages containing "unauthorized" or "access denied" may match
|
|
even if not related to authentication (e.g., file permission errors)
|
|
- Error messages containing these keywords in user-provided content could match
|
|
- Mitigation: HTTP 401 check provides strong signal; string patterns are secondary
|
|
|
|
Real-world validation:
|
|
- Pattern matching has been tested against actual Claude API error responses
|
|
- False positive rate is acceptable given the recovery mechanism (prompt user to re-auth)
|
|
- If false positive occurs, user can simply resume without re-authenticating
|
|
|
|
Args:
|
|
error: The exception to check
|
|
|
|
Returns:
|
|
True if this is an authentication error, False otherwise
|
|
"""
|
|
error_str = str(error).lower()
|
|
|
|
# Check for HTTP 401 with word boundaries to avoid false positives
|
|
if re.search(r"\b401\b", error_str):
|
|
return True
|
|
|
|
# Check for other authentication indicators
|
|
# NOTE: "authentication failed" and "authentication error" are more specific patterns
|
|
# to reduce false positives from generic "authentication" mentions
|
|
return any(
|
|
p in error_str
|
|
for p in [
|
|
"authentication failed",
|
|
"authentication error",
|
|
"unauthorized",
|
|
"invalid token",
|
|
"token expired",
|
|
"authentication_error",
|
|
"invalid_token",
|
|
"token_expired",
|
|
"not authenticated",
|
|
"http 401",
|
|
"does not have access to claude",
|
|
"please login again",
|
|
]
|
|
)
|