Skip to content

ref(seer): Filter private fields from explorer chat API response#113125

Merged
trevor-e merged 1 commit intomasterfrom
trevor-e/ref/explorer-chat-response-filtering
Apr 16, 2026
Merged

ref(seer): Filter private fields from explorer chat API response#113125
trevor-e merged 1 commit intomasterfrom
trevor-e/ref/explorer-chat-response-filtering

Conversation

@trevor-e
Copy link
Copy Markdown
Member

@trevor-e trevor-e commented Apr 15, 2026

The explorer chat GET endpoint was forwarding the full SeerRunState from Seer to users via state.dict(), exposing internal fields and metadata. All models also used extra = "allow" which let any undeclared fields from Seer leak through to the response.

Two changes:

  • extra = "ignore" on models used in the chat response, so undeclared fields from Seer are dropped at parse time rather than leaking through .dict()
  • Field(exclude=True) on some fields so they're omitted from .dict() but still accessible as attributes for internal callers (autofix, night shift, etc.)

Also declares fields the frontend consumes that were previously only passing through as Pydantic extras: owner_user_id, todos, tool_links, tool_results, thinking_content, and ToolCall.id.

Related: https://github.com/getsentry/seer/pull/5803 — this change on the Sentry side makes the Seer-side stripping of fields unnecessary for the chat endpoint.

The explorer chat GET endpoint was forwarding the full SeerRunState
from Seer to users, exposing internal fields like usage/cost data,
metadata, and coding agent state. Additionally, all models used
extra="allow" which let any undeclared fields from Seer leak through.

Change extra to "ignore" so undeclared fields are dropped at parse
time, and mark internal fields (usage, metadata, coding_agents) with
Field(exclude=True) so they are omitted from .dict() but still
accessible as attributes for internal callers like autofix.

Also declares fields the frontend consumes that were previously only
passing through as extras: owner_user_id, todos, tool_links,
tool_results, thinking_content, and ToolCall.id.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 15, 2026
@trevor-e trevor-e marked this pull request as ready for review April 15, 2026 22:00
@trevor-e trevor-e requested a review from a team as a code owner April 15, 2026 22:00
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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 28e56e6. Configure here.

diff: str = ""

class Config:
extra = "allow"
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.

FilePatch still allows extra fields to leak through

Low Severity

FilePatch retains extra = "allow" while every other model in the chat response serialization chain (ExplorerFilePatch, MemoryBlock, SeerRunState, etc.) was changed to extra = "ignore". Since FilePatch is nested inside ExplorerFilePatch which appears in MemoryBlock.file_patches and MemoryBlock.merged_file_patches, any undeclared fields Seer attaches to file patch objects can still leak through state.dict() to the API response.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 28e56e6. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was intentional since there were some nested fields that were a bit painful to add types for right now. Seems fine to keep as-is for the short term, can follow up.

@trevor-e trevor-e merged commit a2b4584 into master Apr 16, 2026
61 checks passed
@trevor-e trevor-e deleted the trevor-e/ref/explorer-chat-response-filtering branch April 16, 2026 16:26
chromy added a commit that referenced this pull request Apr 16, 2026
…nse (#113125)"

This reverts commit a2b4584.

Adding `Field(exclude=True)` to `SeerRunState.metadata` (and `coding_agents`,
`usage`) causes `trigger_push_changes` to raise `SeerPermissionError` — the
`open_pr` path calls `client.get_run(run_id)` which round-trips the state
through `.dict()`, dropping `metadata` and failing the
`state.metadata.get("group_id") != group.id` check in autofix_agent.py, which
the endpoint surfaces as 404.

Breaks `test_open_pr`, `test_open_pr_with_repo_name`, and
`test_open_pr_without_repo_name` in `GroupAutofixEndpointExplorerRoutingTest`.

Agent transcript: https://claudescope.sentry.dev/share/jrltmqqO9R5-5IFIdaBmPVdw_GfoseCtIvlM1JXrb90
@trevor-e trevor-e added the Trigger: Revert Add to a merged PR to revert it (skips CI) label Apr 16, 2026
@getsentry-bot
Copy link
Copy Markdown
Contributor

PR reverted: 0fa9f32

getsentry-bot added a commit that referenced this pull request Apr 16, 2026
…nse (#113125)"

This reverts commit a2b4584.

Co-authored-by: trevor-e <1447798+trevor-e@users.noreply.github.com>
joshuarli added a commit that referenced this pull request Apr 16, 2026
Pydantic BaseModel (and similar metaclass-driven patterns) generates
__init__, dict(), etc. at class definition time. When a test only
instantiates models and calls inherited methods, no lines from the
source file appear in coverage — so the coverage DB never links the
test to the changed file, and the test gets skipped.

Add _find_tests_by_imports(): for each changed non-test .py source
file, grep test directories for "from <module> import" /
"import <module>" and union those results into the coverage-based
selection.

Reproducer: #113125 changed src/sentry/seer/explorer/client_models.py
(Pydantic models only), and test_group_ai_autofix.py was skipped even
though it imports SeerRunState from that file.
rbro112 added a commit that referenced this pull request Apr 17, 2026
A failing test slipped past selective testing earlier (thread).

Upon investigation, this was because there was a change to a model-only
file (`src/sentry/seer/explorer/client_models.py`)
[PR](#113125). In the test file
that shouldn't have been skipped
(`tests/sentry/seer/endpoints/test_group_ai_autofix.py`), one model from
that `client_models.py` file is used (`SeerRunState`), but the usage is
only an instantiation and a `.dict()` call, both pydantic BaseModel
methods.

`test_group_ai_autofix.py` doesn't show up in coverage for 
`client_models.py` , due to no lines of code from `client_models.py`
actually being executed (just base model lines executed).

This addresses this whole by unioning coverage detected test files and
any test files that have a direct import on a changed file. This will
miss transitive dependencies, but this should be at least one additional
line of defense against holes.

---------

Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
joshuarli added a commit that referenced this pull request Apr 22, 2026
Pydantic BaseModel (and similar metaclass-driven patterns) generates
__init__, dict(), etc. at class definition time. When a test only
instantiates models and calls inherited methods, no lines from the
source file appear in coverage — so the coverage DB never links the
test to the changed file, and the test gets skipped.

Add _find_tests_by_imports(): for each changed non-test .py source
file, grep test directories for "from <module> import" /
"import <module>" and union those results into the coverage-based
selection.

Reproducer: #113125 changed src/sentry/seer/explorer/client_models.py
(Pydantic models only), and test_group_ai_autofix.py was skipped even
though it imports SeerRunState from that file.
joshuarli added a commit that referenced this pull request Apr 22, 2026
Pydantic BaseModel (and similar metaclass-driven patterns) generates
__init__, dict(), etc. at class definition time. When a test only
instantiates models and calls inherited methods, no lines from the
source file appear in coverage — so the coverage DB never links the
test to the changed file, and the test gets skipped.

Add _find_tests_by_imports(): for each changed non-test .py source
file, grep test directories for "from <module> import" /
"import <module>" and union those results into the coverage-based
selection.

Reproducer: #113125 changed src/sentry/seer/explorer/client_models.py
(Pydantic models only), and test_group_ai_autofix.py was skipped even
though it imports SeerRunState from that file.
@github-actions github-actions Bot locked and limited conversation to collaborators May 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: Backend Automatically applied to PRs that change backend components Trigger: Revert Add to a merged PR to revert it (skips CI)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants