Skip to content

feat(issues): Add issue.autofix_state search key for autofix overview#120293

Merged
NicoHinderling merged 11 commits into
masterfrom
nh/feat/autofix-state-search-key
Jul 22, 2026
Merged

feat(issues): Add issue.autofix_state search key for autofix overview#120293
NicoHinderling merged 11 commits into
masterfrom
nh/feat/autofix-state-search-key

Conversation

@NicoHinderling

@NicoHinderling NicoHinderling commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Adds a Postgres-only issue-search key, issue.autofix_state, that classifies issues into five mutually exclusive autofix pipeline states:

  • merged — the group's latest autofix run has a merged PR (via the SeerAgentRun → SeerRun → SeerRunPullRequest → PullRequest.state join, webhook-fresh and flag-free)
  • review_prSEER_PR_CREATED activity exists, PR not merged
  • code_changes_readySEER_CODING_COMPLETED, no PR yet
  • solution_readySEER_SOLUTION_COMPLETED, no code yet
  • needs_investigation — a Seer run in the last 30 days with none of the above

The autofix overview page currently classifies issues client-side by fetching each issue's full run state from Seer (two Seer round trips per issue per page load), which makes true per-section counts and server-side filtering impossible. With this key, each overview section becomes a plain filtered issue-stream query with real counts from X-Hits. Milestone signals come from the SEER_* activity rows Sentry already records when Seer pushes step-completion events; precedence is baked into the conditions so every seer-touched issue lands in exactly one state.

The filter logic lives in a Seer-owned module (sentry/seer/autofix/issue_search.py); registration mirrors issue.progress at the standard three points (value converter, QCallbackCondition, SKIP_SNUBA_FIELDS). No migration (the join is already indexed), no new flags, no behavior change for any existing query.

Performance notes (verified against compiled SQL + prod indexes): milestone subqueries hit Activity's (project_id, type) index and plan as hashed anti-joins (NULL-guarded); the Postgres candidate query is capped at 5k. Two known plan-shape assumptions: the merged join relies on the planner anchoring on SeerAgentRun.project_id (PullRequest.state is unindexed), and needs_investigation has no positive anchor — its scan is bounded by the standard issue-search indexes, same cost model as issue.progress.

The frontend consumer (per-section fetches on the autofix overview) follows in a separate PR.

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Jul 22, 2026
Comment thread src/sentry/issues/issue_search.py
NicoHinderling added a commit that referenced this pull request Jul 22, 2026
Each section is now its own issue-stream query against the new search key with true counts from X-Hits, replacing the client-side classification that required two Seer round trips per issue. Card content (prose, diffs, PR state) loads viewport-lazily per card. Removes the Outcome and Needs-attention filters and page-level pagination; sections fetch up to 100 issues each.

Requires the issue.autofix_state backend PR (#120293) to land first.
@NicoHinderling
NicoHinderling marked this pull request as ready for review July 22, 2026 16:04
@NicoHinderling
NicoHinderling requested review from a team as code owners July 22, 2026 16:04
Classifies issues into mutually exclusive autofix pipeline states from SEER_* activity rows and the seer-run pull request join, for the upcoming issue.autofix_state search key.
Postgres-only search key over the autofix state filter so the autofix overview can fetch each section as a filtered issue-stream query with true counts.
Exercises milestone exclusivity, merged-PR precedence, recency-window fallback, multi-value union, and invalid-value rejection through the search backend.
A single null group_id in the milestone subquery turns the negated states' NOT IN into an always-unknown predicate, silently emptying every section that negates it. Unreachable today (Seer activities always carry a group) but guarded like the merged-PR join already is.
@NicoHinderling
NicoHinderling force-pushed the nh/feat/autofix-state-search-key branch from cfea846 to 1366e5c Compare July 22, 2026 16:11
Comment thread src/sentry/seer/autofix/issue_search.py
Verifies the search backend excludes matching groups when the autofix state filter is negated, guarding the QCallbackCondition exclude path end to end.
A previously merged Seer PR permanently classified a group as merged, hiding newer runs whose PRs still await review. Restrict the merged check to each group's most recent autofix run so re-runs after a regression surface again.
Comment thread src/sentry/seer/autofix/issue_search.py
Non-autofix agent runs (chat, explorer) on the same group could become the latest run and knock a merged autofix PR out of the merged state.

@trevor-e trevor-e left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving to unblock you since this isn't wired up anywhere but I would confirm with the issues team before merging. I don't know the downstream ramifications of registering a new search key. I would look into revising the query before merging as well.

Comment thread tests/sentry/issues/test_issue_search.py Outdated
Comment thread tests/sentry/seer/autofix/test_issue_search.py Outdated
coding = _milestone_q(ActivityType.SEER_CODING_COMPLETED, projects)
solution = _milestone_q(ActivityType.SEER_SOLUTION_COMPLETED, projects)

conditions: dict[str, Q] = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is going to result in a pretty inefficient query that might not work in prod. I tested compiling the SQL for this and it repeats the JOIN and IN parts several times. You may want to pull some of this into a CTE first and go from there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now collapsed the repeated milestone queries into a single GROUP BY group_id HAVING scan per state (a95ed1d), since Django's ORM can't express CTEs.

Worst-case single-state query went from 5 subqueries (3 duplicate Activity scans) to 3, and each compiles to hashed subplans over the (project_id, type) index so they execute once per query, not per row. The UI only ever queries one state at a time, so that bounded shape is the hot path.

FWIW though the subplans are hashed and run once per query, not per row, each over the (project_id, type) index with the candidate set capped at 5k, so even without the consolidation, it was already bounded and should still be fast from what i can tell

Remove the constant-equality frozenset test and the e2e precedence test that duplicated unit-level coverage.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 58a914f. Configure here.

Comment thread src/sentry/seer/autofix/issue_search.py
Convert the loop-based converter test to pytest parametrize driven by AUTOFIX_STATE_VALUES, and widen the converter user annotation to match sibling converters.
Each autofix state compiled up to three separate Activity subqueries scanning the same rows. Aggregate milestone flags with GROUP BY group_id HAVING so every state needs at most one Activity scan, cutting the worst single-state query from five subqueries to three.
Comment thread src/sentry/seer/autofix/issue_search.py
An empty list compiled to a blank Q() that matched every group. Return a match-nothing filter instead so callers outside the search pipeline cannot select everything by accident.
@NicoHinderling
NicoHinderling merged commit d8a6d3b into master Jul 22, 2026
69 checks passed
@NicoHinderling
NicoHinderling deleted the nh/feat/autofix-state-search-key branch July 22, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants