Skip to content
1 change: 1 addition & 0 deletions apps/backend/core/error_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def is_rate_limit_error(error: Exception) -> bool:
for p in [
"limit reached",
"rate limit",
"rate_limit", # Catches "Unknown message type: rate_limit_event" from claude_agent_sdk
"too many requests",
"usage limit",
"quota exceeded",
Expand Down
43 changes: 43 additions & 0 deletions apps/backend/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,49 @@
if "_new_stream" in dir():
del _new_stream

def _patch_claude_agent_sdk_rate_limit():
"""Monkey-patch claude_agent_sdk to handle rate_limit_event gracefully.

The bundled SDK raises MessageParseError for unknown message types including
rate_limit_event. This patch returns a SystemMessage instead of raising,
allowing the session to continue and retry after rate limit resets.

Must patch claude_agent_sdk._internal.client.parse_message (the already-bound
module-level name), not just message_parser.parse_message, because the client
uses `from .message_parser import parse_message` at the top level — a direct
name binding that is unaffected by replacing the module attribute alone.
"""
try:
from claude_agent_sdk._internal import message_parser as _mp
from claude_agent_sdk._internal import client as _ic
from claude_agent_sdk.types import SystemMessage as _SystemMessage
import logging as _logging

Check failure on line 89 in apps/backend/run.py

View workflow job for this annotation

GitHub Actions / Python (Ruff)

Ruff (I001)

apps/backend/run.py:86:9: I001 Import block is un-sorted or un-formatted

_logger = _logging.getLogger("claude_agent_sdk.patch")
_orig_parse = _mp.parse_message

def _patched_parse(data):
try:
return _orig_parse(data)
except Exception:
if isinstance(data, dict) and data.get("type") == "rate_limit_event":
_logger.warning(
"Rate limit event received from Claude Code — "
"returning as SystemMessage to allow retry: %s", data
)
return _SystemMessage(subtype="rate_limit_event", data=data)
raise

# Patch both the module attribute AND the already-bound name in _internal.client
_mp.parse_message = _patched_parse
_ic.parse_message = _patched_parse
except Exception:
pass # Never break startup if patch fails


_patch_claude_agent_sdk_rate_limit()


# Validate platform-specific dependencies BEFORE any imports that might
# trigger graphiti_core -> real_ladybug -> pywintypes import chain (ACS-253)
from core.dependency_validator import validate_platform_dependencies
Expand Down
43 changes: 43 additions & 0 deletions apps/backend/runners/spec_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,49 @@
# Add auto-claude to path (parent of runners/)
sys.path.insert(0, str(Path(__file__).parent.parent))


def _patch_claude_agent_sdk_rate_limit():
"""Monkey-patch claude_agent_sdk to handle rate_limit_event gracefully.

The bundled SDK raises MessageParseError for unknown message types including
rate_limit_event. This patch returns a SystemMessage instead of raising,
allowing the session to continue and retry after rate limit resets.

Must patch claude_agent_sdk._internal.client.parse_message (the already-bound
module-level name), not just message_parser.parse_message, because the client
uses `from .message_parser import parse_message` at the top level — a direct
name binding that is unaffected by replacing the module attribute alone.
"""
try:
from claude_agent_sdk._internal import message_parser as _mp
from claude_agent_sdk._internal import client as _ic
from claude_agent_sdk.types import SystemMessage as _SystemMessage
import logging as _logging

Check failure on line 103 in apps/backend/runners/spec_runner.py

View workflow job for this annotation

GitHub Actions / Python (Ruff)

Ruff (I001)

apps/backend/runners/spec_runner.py:100:9: I001 Import block is un-sorted or un-formatted

_logger = _logging.getLogger("claude_agent_sdk.patch")
_orig_parse = _mp.parse_message

def _patched_parse(data):
try:
return _orig_parse(data)
except Exception:
if isinstance(data, dict) and data.get("type") == "rate_limit_event":
_logger.warning(
"Rate limit event received from Claude Code — "
"returning as SystemMessage to allow retry: %s", data
)
return _SystemMessage(subtype="rate_limit_event", data=data)
raise

# Patch both the module attribute AND the already-bound name in _internal.client
_mp.parse_message = _patched_parse
_ic.parse_message = _patched_parse
except Exception:
pass # Never break startup if patch fails


_patch_claude_agent_sdk_rate_limit()

# Validate platform-specific dependencies BEFORE any imports that might
# trigger graphiti_core -> real_ladybug -> pywintypes import chain (ACS-253)
from core.dependency_validator import validate_platform_dependencies
Expand Down
Loading