Skip to content

feat(seer): Add new bulk+single project Seer settings endpoints#114899

Closed
srest2021 wants to merge 28 commits intomasterfrom
srest2021/CW-1285
Closed

feat(seer): Add new bulk+single project Seer settings endpoints#114899
srest2021 wants to merge 28 commits intomasterfrom
srest2021/CW-1285

Conversation

@srest2021
Copy link
Copy Markdown
Member

@srest2021 srest2021 commented May 5, 2026

Fixes CW-1285

Depends on #115037

Adds endpoints for managing per-project Seer settings:

  • GET /api/0/projects/{org}/{project}/seer/settings/ — single-project settings read
  • PUT /api/0/projects/{org}/{project}/seer/settings/ — single-project settings update
  • GET /api/0/organizations/{org}/seer/projects/ — paginated list with search/sort/filter
  • PUT /api/0/organizations/{org}/seer/projects/ — bulk update across multiple projects

Both endpoints use the update_seer_project_settings helper from #115037 to translate high-level fields (agent, integrationId, stoppingPoint, scannerAutomation) into project options.

Supported filters (via query parameter)

Filter Operators Example
id =, !=, IN, NOT IN id:1, id:[1,2,3]
name (free text) =, != my-project
reposCount =, !=, >, <, >=, <= reposCount:>0
stoppingPoint =, !=, IN, NOT IN stoppingPoint:off
agent =, !=, IN, NOT IN agent:seer, !agent:cursor_background_agent

Supported sort fields (via sortBy parameter)

name, -name, reposCount, -reposCount, agent, -agent, stoppingPoint, -stoppingPoint

Example: GET single project

GET /api/0/projects/sentry/test-seer-settings/seer/settings/
{
    "projectId": 2,
    "projectSlug": "test-seer-settings",
    "agent": "seer",
    "integrationId": null,
    "stoppingPoint": "code_changes",
    "scannerAutomation": true,
    "reposCount": 1
}

Example: PUT single project

PUT /api/0/projects/sentry/test-seer-settings/seer/settings/

{"stoppingPoint": "open_pr"}
{
    "projectId": 2,
    "projectSlug": "test-seer-settings",
    "agent": "seer",
    "integrationId": null,
    "stoppingPoint": "open_pr",
    "scannerAutomation": true,
    "reposCount": 1
}

Example: PUT bulk update

PUT /api/0/organizations/sentry/seer/projects/

{"query": "agent:cursor_background_agent reposCount>:0", "stoppingPoint": "open_pr"}

Returns 204 No Content.

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 5, 2026

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label May 5, 2026
Comment thread src/sentry/seer/endpoints/project_settings.py
Comment thread src/sentry/seer/endpoints/project_settings.py
@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label May 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🚨 Warning: This pull request contains Frontend and Backend changes!

It's discouraged to make changes to Sentry's Frontend and Backend in a single pull request. The Frontend and Backend are not atomically deployed. If the changes are interdependent of each other, they must be separated into two pull requests and be made forward or backwards compatible, such that the Backend or Frontend can be safely deployed independently.

Have questions? Please ask in the #discuss-dev-infra channel.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

📊 Type Coverage Diff

✅ No new type safety issues introduced. Coverage: 93.45%

Comment thread src/sentry/seer/endpoints/project_settings.py Fixed
Comment thread src/sentry/seer/endpoints/project_settings.py Fixed
@getsentry getsentry deleted a comment from github-actions Bot May 5, 2026
@getsentry getsentry deleted a comment from github-actions Bot May 5, 2026
Comment thread src/sentry/seer/endpoints/project_settings.py
def _annotate_queryset(queryset):
# ProjectOption.value is a LegacyTextJSONField — a text column storing JSON.
# Use LegacyTextJSONField as output_field. Coalesce fallback values must also
# be JSON-encoded to match what the DB stores.
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 method is kind of ugly with the JSON stuff, but not sure how else to write this--we need the annotations for ordering (otherwise I would just filter in-memory using projectoptions get_option or get_value_bulk).

Basically the problem I ran into was that LegacyTextJSONField output has quotes, but setting a Coalesce default to Value(...) doesn't work for comparison because that wouldn't have quotes. So I am json.dumps'ing the hardcoded defaults.

@getsentry getsentry deleted a comment from github-actions Bot May 5, 2026
Comment thread src/sentry/seer/endpoints/project_settings.py
@srest2021
Copy link
Copy Markdown
Member Author

@sentry review

Comment thread src/sentry/seer/autofix/utils.py Outdated
@srest2021
Copy link
Copy Markdown
Member Author

@sentry review

Comment thread src/sentry/seer/autofix/utils.py
@srest2021 srest2021 changed the base branch from master to srest2021/CW-1285-utils-prep May 6, 2026 23:54
@srest2021 srest2021 force-pushed the srest2021/CW-1285 branch from f50de0a to 8829ef6 Compare May 7, 2026 00:03
@srest2021
Copy link
Copy Markdown
Member Author

@sentry review

@srest2021 srest2021 marked this pull request as ready for review May 7, 2026 18:53
@srest2021 srest2021 requested review from a team as code owners May 7, 2026 18:53
@srest2021 srest2021 requested review from JoshFerge and ryan953 May 7, 2026 18:54
Comment thread src/sentry/seer/endpoints/project_settings.py
Comment thread src/sentry/seer/endpoints/project_settings.py
Comment thread src/sentry/seer/endpoints/project_settings.py
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 e7fc0b6. Configure here.

data = serializer.validated_data
search_query = data.pop("query")

accessible_projects = self.get_projects(request, organization)
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.

PUT handler misses projects visible in GET response

Medium Severity

The get handler calls self.get_projects(request, organization, include_all_accessible=True) but the put handler calls self.get_projects(request, organization) without include_all_accessible=True. This means the PUT handler operates on a narrower set of projects (only team-scoped) than the GET handler shows. A user could see a project in the paginated GET list, craft a bulk update targeting it, and the update would silently skip it because get_projects without include_all_accessible doesn't return it.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e7fc0b6. Configure here.

srest2021 added a commit that referenced this pull request May 7, 2026
Relates to CW-1285

Tldr, add an unused helper and prep stuff for
#114899.

Adds `update_seer_project_settings`, a helper that translates high-level
Seer settings (agent, stopping point, scanner automation) into the
underlying project options. This centralizes the write logic so the
upcoming bulk and single-project settings endpoints can share it instead
of duplicating option-level writes.

- **agent=seer**: clears required handoff options (point, target,
integration_id)
- **agent=cursor/claude**: sets handoff config, requires an integration
ID
- **stoppingPoint (non-off)**: sets tuning to MEDIUM and stores the
stopping point value
- **stoppingPoint=off**: sets tuning to OFF; leaves stopping point and
auto_create_pr alone so that reenabling tuning restores the prior state
- **stoppingPoint=open_pr**: sets auto_create_pr=True; any other
stopping point clears it. Safe to set unconditionally since
[auto_create_pr is only read when all other handoff options are
non-null.](https://github.com/getsentry/sentry/blob/d254733b163432ca88ae796c05d415f97064cbbc/src/sentry/seer/autofix/utils.py#L622)
- **stoppingPoint omitted**: tuning and auto_create_pr are left
untouched
- Options set to their registered default are deleted rather than stored
Base automatically changed from srest2021/CW-1285-utils-prep to master May 7, 2026 22:35
| Q(_handoff_target=Value(json.dumps(None), output_field=LegacyTextJSONField())),
then=Value(json.dumps(AutomationCodingAgent.SEER)),
),
default="_handoff_target",
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.

Bug: The Case expression for the agent annotation uses a literal string default="_handoff_target" instead of a column reference, which will cause filtering and sorting by agent to fail.
Severity: HIGH

Suggested Fix

Wrap the default value in an F() expression to correctly reference the _handoff_target column value. Change default="_handoff_target" to default=F("_handoff_target") and import F from django.db.models.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry/seer/endpoints/project_settings.py#L209

Potential issue: In `_annotate_queryset`, the `agent` annotation is defined using a
Django `Case` expression. The `default` is set to the literal string
`"_handoff_target"`. Because the `output_field` is `LegacyTextJSONField`, Django does
not automatically convert this string to an `F()` expression. As a result, when a
project has an external agent, the annotated `agent` field will contain the literal
string `"_handoff_target"` instead of the actual value from the `_handoff_target`
column. This breaks filtering and sorting by the `agent` field in the
`OrganizationSeerProjectSettingsEndpoint` for both GET and PUT requests, as they will
operate on the incorrect literal string.

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.

Covered in test_get_sort_by_agent

constantinius pushed a commit that referenced this pull request May 8, 2026
Relates to CW-1285

Tldr, add an unused helper and prep stuff for
#114899.

Adds `update_seer_project_settings`, a helper that translates high-level
Seer settings (agent, stopping point, scanner automation) into the
underlying project options. This centralizes the write logic so the
upcoming bulk and single-project settings endpoints can share it instead
of duplicating option-level writes.

- **agent=seer**: clears required handoff options (point, target,
integration_id)
- **agent=cursor/claude**: sets handoff config, requires an integration
ID
- **stoppingPoint (non-off)**: sets tuning to MEDIUM and stores the
stopping point value
- **stoppingPoint=off**: sets tuning to OFF; leaves stopping point and
auto_create_pr alone so that reenabling tuning restores the prior state
- **stoppingPoint=open_pr**: sets auto_create_pr=True; any other
stopping point clears it. Safe to set unconditionally since
[auto_create_pr is only read when all other handoff options are
non-null.](https://github.com/getsentry/sentry/blob/d254733b163432ca88ae796c05d415f97064cbbc/src/sentry/seer/autofix/utils.py#L622)
- **stoppingPoint omitted**: tuning and auto_create_pr are left
untouched
- Options set to their registered default are deleted rather than stored
"sentry:seer_automation_handoff_integration_id",
"sentry:seer_automation_handoff_auto_create_pr",
"sentry:autofix_automation_tuning",
"sentry:seer_scanner_automation",
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.

this didn't exist before?

Copy link
Copy Markdown
Member Author

@srest2021 srest2021 May 8, 2026

Choose a reason for hiding this comment

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

It's not part of the SeerProjectPreference model (I wrote this list so I could use it in the original SeerProjectRepository read/write helpers which use SeerProjectPreference), but since it's an autofix-specific setting I figured I'd add it to try and centralize all of our Seer settings into one place.

Copy link
Copy Markdown
Member

@JoshFerge JoshFerge left a comment

Choose a reason for hiding this comment

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

there is a lot in this PR, and it looks good, but i'd like to take my time with reviewing it. is it possible to:

  • split the PR up into first an endpoint which contains the seer project endpont, and a second one for the bulk endpoint? that might make it easier to review

@srest2021
Copy link
Copy Markdown
Member Author

Splitting into #115230 and #115234

@srest2021 srest2021 closed this May 8, 2026
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 Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants