Skip to content

Commit 41884f8

Browse files
authored
fix(seer): Don't request embed widgets for entrypoint (Slack) agent runs (#119396)
Seer Agent conversations started from 'text' entrypoints like Slack reuse the same `SeerAgentClient` as the web. In these cases we should not emit inline embed widgets as Markdoc tags (e.g. formatted timestamps) as Slack renders the response as plain markdown and can't display these, so the raw tags leak into the message as text. Disable embeds in these cases. Agent transcript: https://claudescope.sentry.dev/share/EgqobwvsuPhAHUCk0rebsdyKfG4ReFNHoD_E9KIA2dQ
1 parent d76a880 commit 41884f8

3 files changed

Lines changed: 69 additions & 2 deletions

File tree

src/sentry/seer/agent/client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ def execute(cls, organization: Organization, run_id: int) -> None:
310310
enable_coding: Include code editing tools. When False, the agent cannot make code changes. Default is False. If enable_coding is True and the organization does not have the enable_seer_coding option, a SeerPermissionError will be raised.
311311
code_review_enabled: Expose the review_code_changes tool, which spawns a reviewer agent to check accumulated code edits before finalizing. Only useful alongside enable_coding. Default is False.
312312
max_iterations: Optional maximum number of agent iterations. Useful for lightweight/fast runs that don't need full exploration depth.
313+
enable_embeds: Allow the agent to emit rich inline embed widgets (e.g. formatted timestamps) as Markdoc tags. Only enable for surfaces that render these embeds (the Explorer chat in the Sentry UI). Disable for plaintext/markdown surfaces like Slack, where the tags would leak as raw text. Default is True.
313314
"""
314315

315316
def __init__(
@@ -330,6 +331,7 @@ def __init__(
330331
enable_code_mode_tools: str = "off",
331332
code_review_enabled: bool = False,
332333
max_iterations: int | None = None,
334+
enable_embeds: bool = True,
333335
):
334336
self.organization = organization
335337
self.user = user
@@ -345,6 +347,7 @@ def __init__(
345347
self.enable_code_mode_tools = enable_code_mode_tools
346348
self.code_review_enabled = code_review_enabled
347349
self.max_iterations = max_iterations
350+
self.enable_embeds = enable_embeds
348351

349352
if enable_coding and not organization.get_option("sentry:enable_seer_coding", True):
350353
raise SeerPermissionError("Seer coding is not enabled for this organization")
@@ -613,7 +616,7 @@ def _build_agent_run_options(self, override_ce_enable: bool = True) -> dict[str,
613616
):
614617
opts["enable_tool_summary"] = True
615618

616-
if features.has(
619+
if self.enable_embeds and features.has(
617620
"organizations:seer-explorer-embeds",
618621
self.organization,
619622
actor=self.user,
@@ -726,7 +729,7 @@ def continue_run(
726729
):
727730
agent_run_options["enable_tool_summary"] = True
728731

729-
if features.has(
732+
if self.enable_embeds and features.has(
730733
"organizations:seer-explorer-embeds",
731734
self.organization,
732735
actor=self.user,

src/sentry/seer/entrypoints/operator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,10 @@ def trigger_agent(
495495
is_interactive=True,
496496
enable_coding=False,
497497
enable_code_mode_tools=enable_code_mode_tools,
498+
# Entrypoints (e.g. Slack) render responses as plain markdown and
499+
# can't display embed widgets, so the raw Markdoc tags would leak as
500+
# text. Don't ask the agent to emit them in the first place.
501+
enable_embeds=False,
498502
)
499503
except SeerPermissionError as e:
500504
with SeerOperatorEventLifecycleMetric(

tests/sentry/seer/agent/test_agent_client.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,66 @@ def test_start_run_passes_enable_pr_context_tools(
255255
body = mock_post.call_args[0][0]
256256
assert body["agent_run_options"]["enable_pr_context_tools"] is True
257257

258+
@patch("sentry.seer.agent.client.has_seer_access_with_detail")
259+
@patch("sentry.receivers.outbox.cell.make_agent_chat_request")
260+
@patch("sentry.seer.agent.client.collect_user_org_context")
261+
@with_feature("organizations:seer-explorer-embeds")
262+
def test_start_run_includes_embed_widgets_by_default(
263+
self, mock_collect_context, mock_post, mock_access
264+
):
265+
mock_access.return_value = (True, None)
266+
mock_collect_context.return_value = {"user_id": self.user.id}
267+
mock_post.return_value = self._mock_run_response()
268+
269+
client = SeerAgentClient(self.organization, self.user)
270+
client.start_run("Test query")
271+
272+
body = mock_post.call_args[0][0]
273+
assert "embed_widgets" in body["agent_run_options"]
274+
275+
@patch("sentry.seer.agent.client.has_seer_access_with_detail")
276+
@patch("sentry.receivers.outbox.cell.make_agent_chat_request")
277+
@patch("sentry.seer.agent.client.collect_user_org_context")
278+
@with_feature("organizations:seer-explorer-embeds")
279+
def test_start_run_excludes_embed_widgets_when_disabled(
280+
self, mock_collect_context, mock_post, mock_access
281+
):
282+
mock_access.return_value = (True, None)
283+
mock_collect_context.return_value = {"user_id": self.user.id}
284+
mock_post.return_value = self._mock_run_response()
285+
286+
client = SeerAgentClient(self.organization, self.user, enable_embeds=False)
287+
client.start_run("Test query")
288+
289+
body = mock_post.call_args[0][0]
290+
assert "embed_widgets" not in body["agent_run_options"]
291+
292+
@patch("sentry.seer.agent.client.has_seer_access_with_detail")
293+
@patch("sentry.seer.agent.client.make_agent_chat_request")
294+
@with_feature("organizations:seer-explorer-embeds")
295+
def test_continue_run_includes_embed_widgets_by_default(self, mock_post, mock_access):
296+
mock_access.return_value = (True, None)
297+
mock_post.return_value = self._mock_run_response()
298+
299+
client = SeerAgentClient(self.organization, self.user)
300+
client.continue_run(123, "Follow-up query")
301+
302+
body = mock_post.call_args[0][0]
303+
assert "embed_widgets" in body["agent_run_options"]
304+
305+
@patch("sentry.seer.agent.client.has_seer_access_with_detail")
306+
@patch("sentry.seer.agent.client.make_agent_chat_request")
307+
@with_feature("organizations:seer-explorer-embeds")
308+
def test_continue_run_excludes_embed_widgets_when_disabled(self, mock_post, mock_access):
309+
mock_access.return_value = (True, None)
310+
mock_post.return_value = self._mock_run_response()
311+
312+
client = SeerAgentClient(self.organization, self.user, enable_embeds=False)
313+
client.continue_run(123, "Follow-up query")
314+
315+
body = mock_post.call_args[0][0]
316+
assert "embed_widgets" not in body["agent_run_options"]
317+
258318
@patch("sentry.seer.agent.client.has_seer_access_with_detail")
259319
def test_init_category_key_only_raises_error(self, mock_access):
260320
"""Test that ValueError is raised when only category_key is provided"""

0 commit comments

Comments
 (0)