diff --git a/.sampo/changesets/claude-agent-sdk-graceful-fallback.md b/.sampo/changesets/claude-agent-sdk-graceful-fallback.md new file mode 100644 index 00000000..4d78b58a --- /dev/null +++ b/.sampo/changesets/claude-agent-sdk-graceful-fallback.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +fix: graceful fallback in claude_agent_sdk query wrapper when PostHog is not configured diff --git a/posthog/ai/claude_agent_sdk/__init__.py b/posthog/ai/claude_agent_sdk/__init__.py index 5fe1828d..170a08c0 100644 --- a/posthog/ai/claude_agent_sdk/__init__.py +++ b/posthog/ai/claude_agent_sdk/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union if TYPE_CHECKING: @@ -17,6 +18,8 @@ from posthog.ai.claude_agent_sdk.client import PostHogClaudeSDKClient from posthog.ai.claude_agent_sdk.processor import PostHogClaudeAgentProcessor +log = logging.getLogger("posthog") + __all__ = [ "PostHogClaudeAgentProcessor", "PostHogClaudeSDKClient", @@ -113,13 +116,28 @@ async def query( print(message) ``` """ - processor = PostHogClaudeAgentProcessor( - client=posthog_client, - distinct_id=posthog_distinct_id, - privacy_mode=posthog_privacy_mode, - groups=posthog_groups, - properties={}, - ) + from claude_agent_sdk import query as original_query + + try: + processor = PostHogClaudeAgentProcessor( + client=posthog_client, + distinct_id=posthog_distinct_id, + privacy_mode=posthog_privacy_mode, + groups=posthog_groups, + properties={}, + ) + except ValueError as e: + # PostHog is not configured (missing API key); fall back to the + # plain SDK so callers are never broken by missing instrumentation. + log.warning( + "PostHog instrumentation disabled: %s — falling back to plain claude_agent_sdk.query()", + e, + ) + async for message in original_query( + prompt=prompt, options=options, transport=transport + ): + yield message + return async for message in processor.query( prompt=prompt, diff --git a/posthog/test/ai/claude_agent_sdk/test_processor.py b/posthog/test/ai/claude_agent_sdk/test_processor.py index f36283c0..74bfc1e0 100644 --- a/posthog/test/ai/claude_agent_sdk/test_processor.py +++ b/posthog/test/ai/claude_agent_sdk/test_processor.py @@ -19,6 +19,7 @@ from posthog.ai.claude_agent_sdk import ( PostHogClaudeAgentProcessor, instrument, + query as posthog_query, ) CLAUDE_AGENT_SDK_AVAILABLE = True @@ -580,3 +581,41 @@ async def fake_query_capture(**kwargs): pass assert captured_options.get("options").include_partial_messages is True + + +class TestQueryGracefulFallback: + @pytest.mark.asyncio + async def test_falls_back_to_original_query_when_posthog_not_configured(self): + result_msg = _make_result_message() + messages_from_sdk = [result_msg] + + with ( + patch( + "posthog.ai.claude_agent_sdk.PostHogClaudeAgentProcessor", + side_effect=ValueError("API key is required"), + ), + patch( + "claude_agent_sdk.query", + side_effect=lambda **kwargs: _fake_query(messages_from_sdk), + ), + ): + collected = [] + async for msg in posthog_query( + prompt="Hello", options=ClaudeAgentOptions() + ): + collected.append(msg) + + assert len(collected) == 1 + assert collected[0] is result_msg + + @pytest.mark.asyncio + async def test_non_config_errors_propagate(self): + with patch( + "posthog.ai.claude_agent_sdk.PostHogClaudeAgentProcessor", + side_effect=RuntimeError("unexpected init bug"), + ): + with pytest.raises(RuntimeError, match="unexpected init bug"): + async for _ in posthog_query( + prompt="Hello", options=ClaudeAgentOptions() + ): + pass