Conversation
Previously, all exceptions from downstream services (Sentry/Snuba) were being caught and re-raised as ValidationError (400), which incorrectly represented issues like rate limits (429) or internal errors (500) as bad request parameters. This change: - Adds a handler to re-raise any REST framework APIException directly, preserving their status codes - Changes the generic exception handler to raise APIException (500) instead of ValidationError (400) - Adds tests to verify that REST framework exceptions are properly re-raised and generic exceptions return 500 This allows Seer to properly differentiate between client errors (4xx) and server errors (5xx) from downstream services. Co-authored-by: Andrew Liu <aliu39@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Test doesn't exercise the new APIException code path
- Mocked in_test_environment() to return False so the test properly exercises the APIException conversion code path instead of re-raising the original exception.
Or push these changes by commenting:
@cursor push bbfaa3b8be
Preview (bbfaa3b8be)
diff --git a/tests/sentry/seer/endpoints/test_seer_rpc.py b/tests/sentry/seer/endpoints/test_seer_rpc.py
--- a/tests/sentry/seer/endpoints/test_seer_rpc.py
+++ b/tests/sentry/seer/endpoints/test_seer_rpc.py
@@ -112,9 +112,12 @@
path = self._get_path("get_organization_slug")
data: dict[str, Any] = {"args": {"org_id": 1}, "meta": {}}
- with patch(
- "sentry.seer.endpoints.seer_rpc.SeerRpcServiceEndpoint._dispatch_to_local_method"
- ) as mock_dispatch:
+ with (
+ patch(
+ "sentry.seer.endpoints.seer_rpc.SeerRpcServiceEndpoint._dispatch_to_local_method"
+ ) as mock_dispatch,
+ patch("sentry.seer.endpoints.seer_rpc.in_test_environment", return_value=False),
+ ):
mock_dispatch.side_effect = RuntimeError("Unexpected internal error")
response = self.client.post(This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 388d7ab. Configure here.
|
Re cursor: not sure how to simulate non-test environment from a test.. code seems straightforward enough to not cover |
trevor-e
left a comment
There was a problem hiding this comment.
re: testing, I think you could patch in_test_environment() to return false?
| ): | ||
| with patch( | ||
| "sentry.seer.endpoints.seer_rpc.SeerRpcServiceEndpoint._dispatch_to_local_method" | ||
| ) as mock_dispatch: | ||
| mock_dispatch.side_effect = RuntimeError("Unexpected internal error") | ||
|
|
||
| response = self.client.post( | ||
| path, data=data, HTTP_AUTHORIZATION=self.auth_header(path, data) | ||
| ) | ||
|
|
||
| assert response.status_code == 500 |
There was a problem hiding this comment.
Bug: The test test_generic_exceptions_return_500 crashes when in_test_environment() is true because a raw RuntimeError is re-raised, preventing the response assertion from being executed.
Severity: LOW
Suggested Fix
The test should be modified to handle the expected RuntimeError when in_test_environment() returns True. This can be done by wrapping the self.client.post(...) call in a pytest.raises(RuntimeError) context manager for the test environment case. This will correctly assert that the exception is raised as intended, allowing the test to pass and properly validate the code path.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: tests/sentry/seer/endpoints/test_seer_rpc.py#L115-L129
Potential issue: The test `test_generic_exceptions_return_500` is designed to check for
a 500 error response. However, it loops through `is_test_environment` being `True` and
`False`. When `is_test_environment` is mocked to `True`, the code under test re-raises a
raw `RuntimeError`. This exception is not handled by the DRF exception handler.
Consequently, the Django test client re-raises the exception, causing the test to crash
at the `self.client.post(...)` call. The assertion `assert response.status_code == 500`
is never reached because the `response` variable is never assigned. This means the test
fails to validate the intended behavior for the test environment scenario.
Did we get this right? 👍 / 👎 to inform future reviews.
…rve rest APIException (#112435) <!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Problem When Seer makes RPC calls to Sentry and encounters downstream errors (like 429 rate limits or 500 errors from Snuba), the current code catches all exceptions and re-raises them as `ValidationError` (400). This misrepresents the actual issue, making it appear as if the client sent bad request parameters when the real cause is a downstream service error. Example issue: https://sentry.dev.getsentry.net:7999/issues/7380803020/events/d3e6e1d5ddf44d7b94c86e6ce2435ea6/ ## Solution This PR fixes the exception handling in the seer RPC endpoint to raise a generic 500 APIException by default, implying an error happened on sentry server. Also re-raises APIException's (base class for rest framework excs) so developers can bubble their own status codes up to response for Seer. 1. **Added a handler to re-raise REST framework exceptions directly** - Any `APIException` (which includes `Throttled`, custom API exceptions, etc.) is now re-raised as-is, preserving its status code 2. **Changed generic exception handler to raise `APIException` (500) instead of `ValidationError` (400)** - Unknown errors now correctly return 500 Internal Server Error instead of 400 Bad Request ### Changes - Modified `SeerRpcServiceEndpoint.post()` in `src/sentry/seer/endpoints/seer_rpc.py`: - Added `except APIException: raise` handler before the generic exception handler - Changed `raise ValidationError from e` to `raise APIException from e` for generic exceptions - Added test cases in `tests/sentry/seer/endpoints/test_seer_rpc.py`: - `test_rest_framework_exceptions_are_reraised` - verifies custom API exceptions preserve their status codes - `test_generic_exceptions_return_500` - verifies generic exceptions return 500 instead of 400 ## Testing Pre-commit checks pass ✅ <!-- CURSOR_AGENT_PR_BODY_END --> [Slack Thread](https://sentry.slack.com/archives/C0AAF0JD0GK/p1775600719171269?thread_ts=1775600719.171269&cid=C0AAF0JD0GK) <div><a href="https://cursor.com/agents/bc-8bf9c0b1-d369-5efa-81a7-a650bb7e83cc"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-8bf9c0b1-d369-5efa-81a7-a650bb7e83cc"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Andrew Liu <aliu39@users.noreply.github.com>


Problem
When Seer makes RPC calls to Sentry and encounters downstream errors (like 429 rate limits or 500 errors from Snuba), the current code catches all exceptions and re-raises them as
ValidationError(400). This misrepresents the actual issue, making it appear as if the client sent bad request parameters when the real cause is a downstream service error.Example issue: https://sentry.dev.getsentry.net:7999/issues/7380803020/events/d3e6e1d5ddf44d7b94c86e6ce2435ea6/
Solution
This PR fixes the exception handling in the seer RPC endpoint to raise a generic 500 APIException by default, implying an error happened on sentry server. Also re-raises APIException's (base class for rest framework excs) so developers can bubble their own status codes up to response for Seer.
APIException(which includesThrottled, custom API exceptions, etc.) is now re-raised as-is, preserving its status codeAPIException(500) instead ofValidationError(400) - Unknown errors now correctly return 500 Internal Server Error instead of 400 Bad RequestChanges
SeerRpcServiceEndpoint.post()insrc/sentry/seer/endpoints/seer_rpc.py:except APIException: raisehandler before the generic exception handlerraise ValidationError from etoraise APIException from efor generic exceptionstests/sentry/seer/endpoints/test_seer_rpc.py:test_rest_framework_exceptions_are_reraised- verifies custom API exceptions preserve their status codestest_generic_exceptions_return_500- verifies generic exceptions return 500 instead of 400Testing
Pre-commit checks pass ✅
Slack Thread