feat(seer): Add trigger_explorer method to SeerOperator#109675
feat(seer): Add trigger_explorer method to SeerOperator#109675alexsohn1126 merged 237 commits intomasterfrom
Conversation
Add organizations:seer-slack-explorer flag to gate the new Slack @mention functionality that allows users to interact with Seer Explorer by mentioning the Sentry bot. Refs ISWF-2016 Co-Authored-By: Claude <noreply@anthropic.com>
Add three new methods to SlackIntegration for Seer Explorer @mentions: - get_thread_history(): Fetch thread replies via conversations.replies - add_reaction(): Add emoji reaction (idempotent) - remove_reaction(): Remove emoji reaction (idempotent) Methods check installation scopes and fail silently if scope is missing, logging a warning. Extended scopes (reactions:write, channels:history, groups:history, app_mentions:read) are gated by slack.extended-scopes-enabled option, which controls whether new installations request these scopes. Refs ISWF-2017 Co-Authored-By: Claude <noreply@anthropic.com>
Previously the integration metadata always stored `identity_oauth_scopes` regardless of what scopes Slack actually granted. Now we parse the `scope` field from the OAuth response to store the actual granted scopes, enabling proper scope checking for installations that have been upgraded to extended scopes. Also adds scopes to debug metadata keys for troubleshooting. Co-Authored-By: Claude <noreply@anthropic.com>
…ethods-for-thread-history-and-reactions
… scopes When the OAuth response doesn't include granted scopes, fall back to identity_oauth_scopes (old permissions) instead of _get_oauth_scopes() which includes extended permissions.
Add a second generic (ExplorerCachePayloadT) to SeerEntrypoint and SeerOperator, and define Explorer lifecycle methods on the protocol: on_trigger_explorer_error, on_trigger_explorer_success, create_explorer_cache_payload, and on_explorer_update. ISWF-2020
…ocol SlackEntrypoint and MockEntrypoint now implement the new Explorer methods with no-op stubs. Also fixes MockEntrypoint to use 2 type args. ISWF-2020
…erer Add Explorer completion notification support to the notification platform: - SeerExplorerResponse dataclass and template for Explorer completion data - SEER_EXPLORER_RESPONSE NotificationSource - _render_explorer_response() method in SeerSlackRenderer ISWF-2025
Ref: ISWF-2018 Add routing and handler for Slack `app_mention` events to enable Seer Explorer via @mentions. The handler resolves the organization, checks the `seer-slack-explorer` feature flag, and dispatches a `process_explorer_mention` task with the event context.
Use a more generic name since this task will handle all Slack mentions, not just Explorer-specific ones. Refs ISWF-2018 Co-Authored-By: Claude <noreply@anthropic.com>
4df6820 to
c34a704
Compare
54bbfc6 to
881c751
Compare
881c751 to
c96afad
Compare
c96afad to
740d420
Compare
…thod-to-seeroperator
The get_runs call in trigger_explorer defaulted to only_current_user=True, which meant a second user interacting with an Explorer session in a shared context (e.g. Slack thread) would not find the existing run and would incorrectly create a new one. Pass only_current_user=False so the lookup finds any run matching the category regardless of who started it. Co-Authored-By: Claude Opus 4.6 <noreply@example.com>
| with SeerOperatorEventLifecycleMetric( | ||
| interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_EXPLORER_ERROR, | ||
| entrypoint_key=self.entrypoint.key, | ||
| ).capture(): | ||
| self.entrypoint.on_trigger_explorer_error(error=str(e)) | ||
| lifecycle.record_failure(failure_reason=e) | ||
| return None |
There was a problem hiding this comment.
Oh interesting. What is this entrypoint call doing on failure? I wonder if it needs to be contained to a task if we expect to propagate this to users reliably.
There was a problem hiding this comment.
The entrypoint will send an error message to the thread, with a generic error message.
Maybe we should make this into a task, but at the same time, spinning up a task will require a lot more overhead, so I don't know if it's worth it 🤔
| run_id=run_id, | ||
| ) | ||
| if cache_payload.get("organization_id") != organization.id: | ||
| lifecycle.add_extra("org_mismatch", str(entrypoint_key)) |
There was a problem hiding this comment.
There's a chance this lifecycle will never log this case, so it may be worth explicitly logging, and possibly creating a Sentry issue for this case. This seems serious if we somehow mix up organization IDs at this phase of a run.
There was a problem hiding this comment.
Makes sense. If we find a cache with the same run_id, it better have have the same org_id, otherwise something has indeed gone wrong 😓
Added:
sentry/src/sentry/seer/entrypoints/operator.py
Lines 812 to 814 in 1c563fb
aliu39
left a comment
There was a problem hiding this comment.
explorer client changes and callsites lgtm!
| organization=organization, | ||
| user=user, | ||
| category_key=category_key, | ||
| category_value=category_value, |
There was a problem hiding this comment.
| category_value=category_value, | |
| category_value=category_value, | |
| is_interactive=True, |
You can add this flag as a reminder to the agent there's a user to chat with/get feedback from
Nit can also explicitly set enable_coding=False (already the default) as it's considered experimental
There was a problem hiding this comment.
Thank you! I've changed to include both of your suggestions:
sentry/src/sentry/seer/entrypoints/operator.py
Lines 486 to 494 in 1c563fb
…thod-to-seeroperator
The return is intentional: run_id is globally unique in Seer, so only one entrypoint will ever have a cache entry per run. An org mismatch is anomalous and should abort the entire method. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| with SeerOperatorEventLifecycleMetric( | ||
| interaction_type=SeerOperatorInteractionType.ENTRYPOINT_CREATE_EXPLORER_CACHE_PAYLOAD, | ||
| entrypoint_key=self.entrypoint.key, | ||
| ).capture(): | ||
| cache_payload = self.entrypoint.create_explorer_cache_payload() | ||
|
|
||
| SeerOperatorExplorerCache.set( | ||
| entrypoint_key=str(self.entrypoint.key), | ||
| run_id=run_id, | ||
| cache_payload=cache_payload, | ||
| ) |
There was a problem hiding this comment.
Bug: In trigger_explorer, an unhandled exception after the success notification can cause a silent failure, preventing the user from receiving explorer results.
Severity: HIGH
Suggested Fix
Wrap the calls to create_explorer_cache_payload() and SeerOperatorExplorerCache.set() within a try...except block. This will handle potential exceptions after the success notification has been sent, preventing the process from silently failing and ensuring state consistency. This approach aligns with the exception handling already present earlier in the method.
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/entrypoints/operator.py#L542-L552
Potential issue: In the `trigger_explorer` method, the success callback
`on_trigger_explorer_success()` is invoked before the explorer payload is cached. If a
subsequent operation, such as `create_explorer_cache_payload()` or
`SeerOperatorExplorerCache.set()`, fails and raises an exception (e.g., due to a Redis
error), the exception is not handled. This causes the method to fail after the user has
already been notified of success. As a result, the completion hook will later find no
cache entry and will silently fail to deliver the explorer results to the user, creating
a state inconsistency and a broken user experience.
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.

Summary
SeerExplorerOperatorclass withtrigger_explorer()method that starts or continues Explorer runs viaSeerExplorerClient, checking for existing runs by category (e.g. Slack thread) and routing tocontinue_run()orstart_run()accordinglyon_trigger_explorer_error) and cache payload for theExplorerOnCompletionHookviaSeerOperatorExplorerCacheOPERATOR_TRIGGER_EXPLORER,ENTRYPOINT_ON_TRIGGER_EXPLORER,ENTRYPOINT_CREATE_EXPLORER_CACHE_PAYLOAD,ENTRYPOINT_ON_EXPLORER_UPDATE) toSeerOperatorInteractionTypefor metrics instrumentationSeerExplorerClientandcollect_user_org_contextto acceptRpcUserin addition toUser/AnonymousUser, enabling use from cross-silo contexts like SlackSeerOperatorCompletionHook.execute(): fetch-run-status errors now log instead of aborting, add org-id mismatch guard on cache payloads, and wrapon_explorer_updatecalls with per-entrypoint lifecycle metricsRefs ISWF-2022