fix(slack): Stop an empty Seer mention before it runs an agent turn (#122941)
## Problem
`_handle_seer_prompt` already rejects empty `text`, but it checks
*before* the bot mention is stripped — that happens later, in
`process_mention_for_slack` via `extract_prompt`. A bare `@Seer` has
truthy text `"<@U0BOT>"`, so it passes the guard and reaches Seer as
`query=""`.
Seer then runs a full turn on nothing — an ExplorerIndex query, four
context engine stages, three Vertex embedding calls and an LLM call —
only to answer *"It looks like your message came through empty!"*.
The embedding calls 400 with `The text content is empty.`:
| path | error | events / 30d |
|---|---|---|
| direct google-genai | `ClientError: Empty instances.` |
[SEER-8GR](https://sentry.sentry.io/issues/SEER-8GR) — 393 (296 prod, 97
de) |
| llm-proxy `/embeddings` | `Vertex 400: "The text content is empty."` |
[LLM-PROXY-S](https://sentry.sentry.io/issues/LLM-PROXY-S) |
All 393 are `explorer.category_key:slack_thread` and
`explorer_main_task`. Slack is the only entrypoint affected — the
Explorer chat endpoint already enforces `allow_blank=False`, and every
other caller templates its prompt, so `query` is structurally non-blank
there.
## Fix
Guard at the webhook, before `set_thread_status("is thinking...")` so no
spinner is left running.
The check is a new `has_prompt_content` — is there anything besides
mentions and whitespace — rather than reusing `extract_prompt`.
`extract_prompt` needs the bot's user id, which comes from the event's
`authorizations`; Slack omits that in some cases, and
`test_app_mention_dispatches_task_no_authorizations` already covers it.
With no id the regex degrades to `<@>` and strips nothing, so a bare
mention would have slipped through as the non-empty prompt `"<@U0BOT>"`.
Another user's mention still counts as context whenever real text
accompanies it; only a mention-only message is empty.
Halting silently would have been a regression — the user used to get
*an* answer, however wasteful. `send_empty_prompt_message` sends a fixed
ephemeral nudge, mirroring `send_not_org_member_message`: no link
button, and send failures are logged rather than raised so a Slack
outage can't turn the webhook into a 500. No model, no agent run, no
Vertex call.
Uses a distinct `EMPTY_PROMPT` halt reason rather than
`MISSING_EVENT_DATA`, which is documented as meaning the event was
malformed — a bare mention is a normal user action, and conflating them
would make that metric misleading.
## Test
`tests/sentry/integrations/slack/webhooks/events/` +
`tests/sentry/seer/entrypoints/slack/` — 201 passed. mypy and pre-commit
clean.
New coverage: bare mention halts and sends the nudge; halts with no
`authorizations`; mention-only-with-other-users halts; a mention plus
real text still dispatches; a failing nudge still returns 200 without
dispatching.
## Notes for review
- This guard is the only empty check on the Slack path.
`process_mention_for_slack` re-derives the prompt and `trigger_agent`
forwards it, neither re-checks — so invoking the task directly still
bypasses it.
- Not fixed here: when `authorizations` is missing, the `<@U0BOT>` token
still rides along into the query as prompt noise. Storing the bot user
id on the integration would close that; it's a prompt-quality issue, not
correctness, now that this guard doesn't depend on the id.
🤖 Generated with [Claude Code](https://claude.com/claude-code) S
Sehr committed
65ceb6be686b1d4abb508d6317bcb4abdd5b5f20
Parent: 958a8dd
Committed by GitHub <noreply@github.com>
on 8/28/2026, 4:34:15 PM