Skip to content

feat(events)!: Convert event automation identifiers to filters - #2442

Open
sentry-junior[bot] wants to merge 8 commits into
mainfrom
feat/event-automation-filter-conversion
Open

sentry-junior[bot] wants to merge 8 commits into
mainfrom
feat/event-automation-filter-conversion

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What this does

Today an event automation can point at one thing in two ways:

  • an identifier, such as getsentry/junior#7, or
  • filters, such as repo: getsentry/junior, pullRequest: 7.

#2407 added filters. This PR starts to move everyone to filters, so later we can delete identifiers (#2362).

When someone creates or edits an event automation and gives an identifier, Junior now saves filters that match exactly the same events. The automation runs exactly as before. It does not start to run for more things.

This PR does not change automations that are already saved. They keep running as they are. A separate draft PR, #2447, converts them later.

Refs #2362

What it looks like

Someone asks: "tell me when a review is posted on #7".

The agent still sends this to createEventAutomation:

{ "namespace": "github", "resourceType": "pull_request", "identifier": "getsentry/junior#7", "events": ["pull_request.review.commented"] }

Junior now saves this:

{ "namespace": "github", "events": ["pull_request.review.commented"], "match": { "repo": "getsentry/junior", "pullRequest": 7 } }

The dashboard and the automation details show the filters.

What each plugin converts:

Plugin Saved as filters Stays an identifier
GitHub repo, issue or PR number, release tag, deployment environment identifiers in a form that GitHub webhooks never send
Linear team key, issue key (ENG-123) identifiers in a form that Linear webhooks never send
Vercel project, target, commit identifiers in a form that Vercel webhooks never send
Slack channel none
Sentry none all of them

When Junior keeps an identifier, the automation still works. Its details show a short note that says why.

Sentry keeps identifiers because each Sentry identifier contains an org name, and Junior has no way to check that org. If Junior converted an identifier with an old or wrong org, an automation that never ran could start to run for the wrong things.

Changes to updating an automation

This is the part that changes behavior. It only affects updateEventAutomation, the tool that the agent uses.

Before this PR, a new match replaced all saved filters. After conversion, the filters are the thing that keeps an automation pointed at one PR. So "also skip drafts" could quietly turn "PR #7" into "every PR in the repo".

Now match changes only the filters that it names:

Update Saved before Saved after
match: { isDraft: false } repo, pullRequest: 7 repo, pullRequest: 7, isDraft: false
match: { pullRequest: null } repo, pullRequest: 7 repo (every PR in the repo)
match: { pullRequest: [7, 8] } repo, pullRequest: 7 repo, pullRequest: [7, 8]
match: {} anything no change
match: { repo: null } repo, pullRequest: 7 rejected: GitHub PR events need repo

The identifier rules from #2407 stay the same: leave out identifier to keep it, and set it to null to drop it.

The dashboard editor shows every filter and saves the whole trigger, so it works the same as before.

Junior now also rejects a trigger that can never run. An example is identifier getsentry/junior#7 with filter pullRequest: 5. The error names the filter. Before, Junior saved the trigger, and it never ran.

What might break

  • Agent updates that clear filters. match: {} used to remove all filters. Now it does nothing. To remove a filter, set it to null.
  • Agent updates that replace filters. A new match used to drop the filters that it left out. Now they stay.
  • Triggers that contradict themselves now fail to save with an error, instead of saving and never running.
  • Rollback. No release has feat(events)!: Let event automations run for any resource #2407 yet. Release 0.266.0 and earlier cannot read an automation with no identifier: one such row breaks the automation list for its whole Slack workspace. Once this ships and people create or edit automations, rolling back past feat(events)!: Let event automations run for any resource #2407 has that problem. feat(events)!: Let event automations run for any resource #2407 alone already has the same limit. Each change also saves a version with the old trigger, so a person can restore it from version history.
  • Plugins. Plugins get a new optional hook, identifierMatch, that turns an identifier into filters. Plugins that do not add it keep identifiers. It is temporary, and a later change removes it with identifiers.

Smaller changes

Checks (head e9711e4c3)

--

View Session [Sentry]

Each plugin can now turn its saved identifiers into the filters that
select the same events (`identifierMatch` in plugin-api). Core uses it to
save new and edited event automation triggers with no identifier, and the
heartbeat converts saved rows one at a time through the locked, versioned
save. A trigger converts only when every selected event gets the same
filters, the filters agree with saved filters, and the result passes the
catalog check. Otherwise the row keeps its identifier and keeps running,
so no trigger gets broader.

Slack channel automations now save a `channelId` filter for their
destination channel. Sentry project identifiers do not convert for metric
alert events, because those events can leave out `projects`. An omitted
`match` on update now keeps the saved filters.

Watches keep exact identifiers. The identifier column stays for slice D.

Refs #2362

Co-Authored-By: David Cramer <david@sentry.io>
@vercel

vercel Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
junior-docs Ready Ready Preview Oct 30, 2026 7:36pm UTC

Request Review

@sentry-evals

sentry-evals Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Sentry Evals

junior-behavioral

Run Status Passed Failed Errored Cost Tokens Duration
Head Error 79 1 2 $0.73 0 569.36s

Base commit faca571 has no eval run to compare against.

junior-guardian

Run Status Passed Failed Errored Cost Tokens Duration
Head Passed 94 0 0 $0.03 0 26.22s

Base commit faca571 has no eval run to compare against.

junior-integration

Run Status Passed Failed Errored Cost Tokens Duration
Head Failed 104 1 0 $0.63 0 365.42s

Base commit faca571 has no eval run to compare against.

junior-router

Run Status Passed Failed Errored Cost Tokens Duration
Head Passed 12 0 0 $0.00 0 3.20s

Base commit faca571 has no eval run to compare against.

A trigger that cannot convert to filters now saves the reason in
`identifierReason` through the same locked, versioned save. The heartbeat
checks at most 50 rows per tick that have an identifier and no saved
reason, so it checks each row once. It skips rows whose identifier column
and saved trigger disagree, because a save would make them broad. Edits
that change the trigger set the reason again.

The trigger description shows the reason as a note, not a problem, in the
dashboard details, Slack details, and listEventAutomations. Rows that keep
their identifier stay active.

The update tool description now says plainly that leaving out match keeps
the saved filters, and only `{}` removes them.

Refs #2362

Co-Authored-By: David Cramer <david@sentry.io>
@sentry-junior sentry-junior Bot changed the title feat(events): Convert event automation identifiers to filters feat(events)!: Convert event automation identifiers to filters Oct 30, 2026
@blacksmith-sh

This comment has been minimized.

Updates now change match filters key by key. A left-out filter keeps its
saved value and null removes it, so an update cannot drop the scope that a
conversion moved into match. A write whose saved filter never matches its
identifier is rejected instead of saved as a trigger that never runs.

Move the heartbeat backfill of saved rows to a follow-up, because no
release can read rows with no identifier yet. Drop the Sentry converter,
because the plugin has no saved install org to check identifiers against.
Pass the one pull request to the lazy GitHub gate, mark identifierMatch as
temporary, and test identifiers that do not rebuild and events that need
different filters.

Refs #2362

Co-Authored-By: David Cramer <david@sentry.io>
The version schema accepts editedBy "junior" and the dashboard shows
Junior as the editor. Nothing writes it yet. The reader ships first, so
the follow-up that converts saved triggers can roll back to this release
and keep version history readable.

Refs #2362

Co-Authored-By: David Cramer <david@sentry.io>
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

4 screenshot changes — 4 changed · 0 added · 0 removed

Review screenshots in Frameshift

Automation Versions · Desktop
Automation Versions · Desktop
Changed
Automation Versions · Mobile
Automation Versions · Mobile
Changed
Conversation Child Agent Drawer · Desktop
Conversation Child Agent Drawer · Desktop
Changed
Conversation Child Agent Drawer · Mobile
Conversation Child Agent Drawer · Mobile
Changed

A repository identifier and a repo filter now save the same trigger with
no identifier, so the eval checks the saved trigger.

Refs #2362
@dcramer

dcramer commented Oct 30, 2026

Copy link
Copy Markdown
Member

the pr description is far too technical and it doesnt explain to me what we're actually doing, what the actual final interface/configurations look like, and what might break

@dcramer

dcramer commented Oct 30, 2026

Copy link
Copy Markdown
Member

is there value in supporting the identifier the way we are? like since its just duplicating matches and it'd be legacy? rying to understand the API interface. maybe we tihnk the simplicity is required to prevent agent errors or something?

what about idempotency keys and deduplication how will that function?

dcramer pushed a commit that referenced this pull request Oct 30, 2026
Since #2440 (`15ee5c130`), an automation run no longer writes its saved
visibility to the Destination row. Only a live Slack signal does. A run
into a channel with no row stores `unknown`, and readers treat it as
private.

In production, the Slack thread where a person created the Automation
records the channel. The eval fixture inserted Automations without that
thread, so their channels stayed `unknown`. The fixture reads results as
`WEB_VIEWER_EMAIL`, who is not a participant. So
`readConversationAccessFromSql` returned `canViewPrivateContent: false`,
and the reporting API hid tool payloads for every scheduled and event
automation run. Tool calls came back with no input and no output
(`result null [4B]` on every tool). `finishAutomationRun` results and
tool arguments were empty. This broke these cases on unrelated branches
(#2435, #2442, fix/flaky-behavioral-evals, chore/remove-slash-command):

- Integration: `Event automation delivery` (no-match silence ×2, blocked
automation) and `Scheduled Delivery` (no message outcome)
- Behavioral: `Scheduled Delivery` (post in this channel, provider that
Junior does not have) and `Sentry Skill Workflows` (creator-bound
scheduled Sentry work)

There is no product change. Fail-closed `unknown` is intended (#2440).
Automations are created from a live Slack turn in their channel, so
production channels have a known visibility.

## Fix

`packages/junior-evals/src/fixture/insert.ts`:
`insertScheduledAutomation` and `insertEventAutomation` now store the
creation thread through the Conversation store with `visibility:
"public"`, as the live Slack event would. No assertions, cases, or
timeouts change.

## Checks

- `pnpm lint`, `pnpm typecheck`, `pnpm --filter @sentry/junior-evals
test` pass.
- A throwaway harness check (not committed) used the real insert
functions. A dispatch-shaped write into a bare `slackChannel()` was
redacted (`unknown`). Channels from either insert function were readable
(`public`).
- The 7 affected eval cases did not run locally: the sandbox has no
`cloudflared` for eval egress. CI must confirm them.

<!-- junior-request-attribution:start -->
via **David Cramer**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:agent%3A5d3914babce549420a1ad7b950c41799 -->

--

[View
Session](https://junior-prod.sentry.dev/conversations/agent%3A5d3914babce549420a1ad7b950c41799)
[[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A5d3914babce549420a1ad7b950c41799/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
…-filter-conversion

# Conflicts:
#	packages/junior-dashboard/e2e/automation-editor.spec.ts
#	packages/junior-dashboard/src/client/pages/automations/AutomationDetails.tsx
#	packages/junior-dashboard/src/client/pages/automations/AutomationVersionsPage.tsx
@dcramer

dcramer commented Oct 30, 2026

Copy link
Copy Markdown
Member

the pr description here needs to be simplified. its somewhat easy to understand but its an essay

This branch was successfully deployed

1 active deployment
Preview – junior-docs — 9987344f Deployed Oct 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high trigger-evals Run evals on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant