feat(explorer): Expose export-indexes via public org seer RPC#113227
feat(explorer): Expose export-indexes via public org seer RPC#113227shruthilayaj merged 9 commits intomasterfrom
Conversation
Add ExplorerExportIndexesRequest TypedDict and
make_explorer_export_indexes_request() to signed_seer_api.py, with
viewer_context as a required parameter so org scoping is always
enforced by the Seer endpoint.
Add export_explorer_indexes() in sentry/seer/explorer/snapshot.py that
proxies to Seer's /v1/automation/explorer/export-indexes endpoint and
register it in public_org_seer_method_registry so it's accessible via
/api/0/organizations/{org}/seer-rpc/export_explorer_indexes/.
Co-Authored-By: Claude <noreply@anthropic.com>
Backend Test FailuresFailures on
|
Co-Authored-By: Claude <noreply@anthropic.com>
Backend Test FailuresFailures on
|
| if response.status >= 400: | ||
| raise SeerApiError("Seer export-indexes request failed", response.status) |
There was a problem hiding this comment.
Bug: SeerApiError from export_explorer_indexes is not handled, causing it to be caught by a generic handler and returned as a ValidationError (HTTP 400).
Severity: MEDIUM
Suggested Fix
Explicitly catch the SeerApiError in the OrganizationSeerRpcEndpoint.post() method and return a more appropriate HTTP status and error message, similar to how it is handled in other Seer-related endpoints.
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: src/sentry/seer/explorer/snapshot.py#L26-L27
Potential issue: The `export_explorer_indexes` function raises a `SeerApiError` if the
Seer service returns a 4xx or 5xx status. However, the calling
`OrganizationSeerRpcEndpoint.post()` method does not explicitly handle this exception
type. As a result, the exception is caught by a generic `except Exception` block, which
converts it into a `ValidationError`, returning an HTTP 400 status to the client. This
masks the original error from Seer (e.g., 401, 404, 500), hindering proper client-side
error handling and making debugging difficult. Other similar endpoints explicitly catch
`SeerApiError`, suggesting this was an oversight.
Did we get this right? 👍 / 👎 to inform future reviews.
| "get_comparative_attribute_distributions": get_comparative_attribute_distributions, | ||
| # | ||
| # Explorer eval tooling | ||
| "export_explorer_indexes": map_org_id_param(export_explorer_indexes), |
There was a problem hiding this comment.
Bug: The RPC endpoint's generic exception handler converts upstream SeerApiError exceptions, including 5xx server errors, into a client-side ValidationError (HTTP 400), masking the original error's nature.
Severity: MEDIUM
Suggested Fix
In organization_seer_rpc.py, add a specific except block for SeerApiError before the generic Exception handler. This new block should inspect the status code within the SeerApiError. If it's a 5xx error, raise an appropriate exception that maps to a 5xx HTTP status. If it's a 4xx error, it can be converted to a ValidationError or ParseError as appropriate. This will ensure that upstream server errors are correctly propagated to the client.
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: src/sentry/seer/endpoints/organization_seer_rpc.py#L125
Potential issue: The RPC endpoint's generic exception handler incorrectly handles errors
from the upstream Seer service. Specifically, when the `export_explorer_indexes`
function raises a `SeerApiError` due to an upstream failure (e.g., a 5xx server error),
the exception is caught and converted into a `ValidationError`. This results in the RPC
endpoint returning an HTTP 400 (Bad Request) to the original caller, incorrectly
signaling a client-side error instead of propagating the upstream server-side failure.
This masks the true nature of the problem, complicates debugging, and leads to
inconsistent error handling between test and production environments.
…ule-level name The registry holds a map_org_id_param closure that captured the original export_explorer_indexes function object at import time. Patching the module-level name in the importer had no effect on the closure, so the real function ran and the mock was never invoked. Patch sentry.seer.explorer.snapshot.make_explorer_export_indexes_request instead, which is resolved at call time inside export_explorer_indexes. Update mock setup to return a response-like object with .status and .json() matching what export_explorer_indexes actually calls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend Test FailuresFailures on
|
Co-authored-by: sentry-warden[bot] <258096371+sentry-warden[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a54eb70. Configure here.
|
|
||
| try: | ||
| return response.json() | ||
| except JSONDecodeError: |
There was a problem hiding this comment.
Wrong JSONDecodeError type caught for urllib3 response
Medium Severity
The except JSONDecodeError block will never execute. JSONDecodeError is imported from sentry.utils.json, which is simplejson.JSONDecodeError. However, response.json() on a urllib3 BaseHTTPResponse uses the standard library's json.loads() internally, which raises json.JSONDecodeError — a completely different class that is not a subclass of simplejson.JSONDecodeError. If Seer returns invalid JSON, the exception propagates uncaught past this handler, bypassing the logger.exception call and the descriptive SeerApiError. The existing codebase pattern (e.g. in anomaly_detection/delete_rule.py) avoids this by manually decoding response.data and using sentry.utils.json.loads().
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a54eb70. Configure here.


Companion to https://github.com/getsentry/seer/pull/5832 (Seer-side PR).
Adds the Sentry side of the explorer index export feature for eval DB seeding.
Changes:
signed_seer_api.py: AddExplorerExportIndexesRequestTypedDict andmake_explorer_export_indexes_request().viewer_contextis a required (non-optional) parameter — callers must always supply it so Seer's org authorization check is guaranteed to pass.seer/explorer/snapshot.py: Newexport_explorer_indexes(org_id)function that proxies to Seer's/v1/automation/explorer/export-indexesendpoint.organization_seer_rpc.py: Registerexport_explorer_indexesinpublic_org_seer_method_registryviamap_org_id_param, exposing it at/api/0/organizations/{org}/seer-rpc/export_explorer_indexes/.How org scoping works:
The
organization_idis always injected from the validated URL org — any caller-suppliedorg_idin args is stripped before dispatch.map_org_id_parammaps it toorg_idbefore calling the function, which passes it in both the request body and as the viewer context. Seer enforces thatctx.organization_id == data.org_idserver-side.Usage (from a Seer eval script):