Skip to content

Conversation

@JerrySentry
Copy link
Contributor

Creating a new table to manage prevent ai configurations, no longer use the previous method of managing it in organization_options. This will give more flexibility to storing configs across multiple git orgs and sentry orgs. Also we got feedback that we want members to be allowed to update config for the PR Review in order to change the type and frequency of reviews. Since this is in beta, there are no billing implications (where a member can create much higher usage that would then get billed). This is aligned with like Seer project settings and Github Integrations settings which allows members to edit.

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Oct 20, 2025
@codecov
Copy link

codecov bot commented Oct 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff             @@
##           master   #101831      +/-   ##
===========================================
+ Coverage   76.91%    80.96%   +4.05%     
===========================================
  Files        8728      8718      -10     
  Lines      388272    387495     -777     
  Branches    24611     24477     -134     
===========================================
+ Hits       298646    313754   +15108     
+ Misses      89268     73393   -15875     
+ Partials      358       348      -10     

@github-actions
Copy link
Contributor

github-actions bot commented Oct 20, 2025

This PR has a migration; here is the generated SQL for src/sentry/prevent/migrations/0001_initial_prevent_ai_configuration.py

for 0001_initial_prevent_ai_configuration in prevent

--
-- Create model PreventAIConfiguration
--
CREATE TABLE "sentry_preventaiconfiguration" ("id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "date_updated" timestamp with time zone NOT NULL, "date_added" timestamp with time zone NOT NULL, "organization_id" bigint NOT NULL, "provider" varchar(64) NOT NULL, "git_organization_id" varchar(255) NOT NULL, "data" jsonb NOT NULL);
CREATE UNIQUE INDEX CONCURRENTLY "sentry_preventaiconfigur_organization_id_provider_eb547f9a_uniq" ON "sentry_preventaiconfiguration" ("organization_id", "provider", "git_organization_id");
ALTER TABLE "sentry_preventaiconfiguration" ADD CONSTRAINT "sentry_preventaiconfigur_organization_id_provider_eb547f9a_uniq" UNIQUE USING INDEX "sentry_preventaiconfigur_organization_id_provider_eb547f9a_uniq";
CREATE INDEX CONCURRENTLY "sentry_preventaiconfiguration_organization_id_a95756f3" ON "sentry_preventaiconfiguration" ("organization_id");

Copy link
Member

@suejung-sentry suejung-sentry left a comment

Choose a reason for hiding this comment

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

nice stuff!
I'm not sure the protocols but we may want to have the migration PR more isolated and do the deletion off the old stuff in a separate PR? Maybe doesn't matter much

@JerrySentry
Copy link
Contributor Author

I'm not sure the protocols but we may want to have the migration PR more isolated and do the deletion off the old stuff in a separate PR? Maybe doesn't matter much

Our old implementation haven't been live yet minus the few testing orgs, we can probably manually migrate it over and delete the old ones.

@JerrySentry JerrySentry marked this pull request as ready for review October 22, 2025 21:38
@JerrySentry JerrySentry requested review from a team as code owners October 22, 2025 21:38
cursor[bot]

This comment was marked as outdated.

Comment on lines 21 to 28
)
from sentry.models.organization import Organization
from sentry.models.repository import Repository
from sentry.prevent.types.config import PREVENT_AI_CONFIG_GITHUB_DEFAULT
from sentry.silo.base import SiloMode
from sentry.types.prevent_config import PREVENT_AI_CONFIG_GITHUB_DEFAULT

logger = logging.getLogger(__name__)

Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: The RPC endpoint reads config from the old organization_options, while the new UI endpoint writes to the new PreventAIConfiguration model, with no data migration.
(Severity: Critical 0.85 | Confidence: 1.00)

🔍 Detailed Analysis

The PreventPrReviewResolvedConfigsEndpoint in overwatch_rpc.py reads configuration from the old organization.get_option storage. However, the new OrganizationPreventGitHubConfigEndpoint writes settings to the new PreventAIConfiguration model. There is no data migration to move existing settings from the old storage to the new model, nor is there any fallback logic for the read endpoint to check the new model. As a result, any configuration changes made through the new UI will be saved to the new model but will be completely ignored by the Overwatch service, which will continue to use old or default settings from the previous storage location.

💡 Suggested Fix

Update the PreventPrReviewResolvedConfigsEndpoint to read from the new PreventAIConfiguration model. Additionally, create a data migration to transfer existing configurations from organization_options to the PreventAIConfiguration table to ensure continuity for existing users.

🤖 Prompt for AI Agent
Fix this bug. In src/sentry/overwatch/endpoints/overwatch_rpc.py at lines 21-28: The
`PreventPrReviewResolvedConfigsEndpoint` in `overwatch_rpc.py` reads configuration from
the old `organization.get_option` storage. However, the new
`OrganizationPreventGitHubConfigEndpoint` writes settings to the new
`PreventAIConfiguration` model. There is no data migration to move existing settings
from the old storage to the new model, nor is there any fallback logic for the read
endpoint to check the new model. As a result, any configuration changes made through the
new UI will be saved to the new model but will be completely ignored by the Overwatch
service, which will continue to use old or default settings from the previous storage
location.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Feature is not live, don't need to make migration.

Comment on lines +87 to +90
response_data: dict[str, Any] = deepcopy(PREVENT_AI_CONFIG_GITHUB_DEFAULT)
response_data["github_organization"][git_organization_id] = request.data

return Response(response_data, status=200)
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: A key in the default config object was renamed from github_organizations to github_organization, but the frontend code was not updated to match.
(Severity: High 0.75 | Confidence: 1.00)

🔍 Detailed Analysis

The default configuration object, PREVENT_AI_CONFIG_GITHUB_DEFAULT, has been modified, renaming the key github_organizations to github_organization. However, multiple frontend components, such as useUpdatePreventAIFeature.tsx and manageReposPanel.tsx, were not updated and still reference the old plural key github_organizations. When the frontend receives a configuration object from the backend using the new singular key, attempts to access config.github_organizations will result in undefined, breaking the UI for managing Prevent AI configurations.

💡 Suggested Fix

Update all frontend references from github_organizations to github_organization to match the new schema defined in the PREVENT_AI_CONFIG_GITHUB_DEFAULT constant. This includes updating type definitions in .tsx files and accessors in hooks and components.

🤖 Prompt for AI Agent
Fix this bug. In src/sentry/prevent/endpoints/pr_review_config.py at lines 87-90: The
default configuration object, `PREVENT_AI_CONFIG_GITHUB_DEFAULT`, has been modified,
renaming the key `github_organizations` to `github_organization`. However, multiple
frontend components, such as `useUpdatePreventAIFeature.tsx` and `manageReposPanel.tsx`,
were not updated and still reference the old plural key `github_organizations`. When the
frontend receives a configuration object from the backend using the new singular key,
attempts to access `config.github_organizations` will result in `undefined`, breaking
the UI for managing Prevent AI configurations.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Frontend code is not live yet, it will be updated next.

Copy link
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.

Bug: AI Config Endpoint Bypasses New Model

The PreventPrReviewResolvedConfigsEndpoint still reads prevent AI configuration from organization.get_option("sentry:prevent_ai_config_github"). This bypasses the new PreventAIConfiguration model, causing the endpoint to return default or stale configurations instead of the actual values, impacting external services.

src/sentry/overwatch/endpoints/overwatch_rpc.py#L75-L88

return auth[0].lower() == self.token_name
def authenticate_token(self, request: Request, token: str) -> tuple[Any, Any]:
compare_signature(request, token)
sentry_sdk.get_isolation_scope().set_tag("overwatch_rpc_auth", True)
return (AnonymousUser(), token)
def _can_use_prevent_ai_features(org: Organization) -> bool:
"""Check if organization has opted in to Prevent AI features."""
hide_ai_features = org.get_option("sentry:hide_ai_features", HIDE_AI_FEATURES_DEFAULT)
pr_review_test_generation_enabled = bool(

src/sentry/overwatch/endpoints/overwatch_rpc.py#L103-L107

"""
publish_status = {
"GET": ApiPublishStatus.EXPERIMENTAL,
}

Fix in Cursor Fix in Web


Copy link
Member

@wedamija wedamija left a comment

Choose a reason for hiding this comment

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

I'd probably suggest splitting out the new migration into a separate pr so that we can get it through more easily


class Meta:
app_label = "prevent"
db_table = "sentry_preventaiconfiguration"
Copy link
Member

Choose a reason for hiding this comment

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

Since this is in your own app, you can feel free to call this prevent_*


__relocation_scope__ = RelocationScope.Excluded

organization_id = BoundedBigIntegerField(db_index=True)
Copy link
Member

Choose a reason for hiding this comment

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

Let's use a proper foreign key here

Copy link
Member

Choose a reason for hiding this comment

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

On a similar note, should we consider which silo this data should live in? I could see a case being made for either region or control, depending on where this is used the most.


organization_id = BoundedBigIntegerField(db_index=True)
provider = models.CharField(max_length=64)
git_organization_id = models.CharField(max_length=255)
Copy link
Member

Choose a reason for hiding this comment

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

Can we link to the integration instead?

organization_id = BoundedBigIntegerField(db_index=True)
provider = models.CharField(max_length=64)
git_organization_id = models.CharField(max_length=255)
data = models.JSONField(default=dict)
Copy link
Member

Choose a reason for hiding this comment

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

Can we use more explicit fields here, instead of a json blob? What kind of data are we putting here?

@JerrySentry
Copy link
Contributor Author

@wedamija

I'd probably suggest splitting out the new migration into a separate pr so that we can get it through more easily

Thanks for looking! I will move the table to a separate PR and make those schema changes. On using explicit fields instead of a json, it would make the table(s) a lot more confusing and inflexible. The current json object structure is defined like this, and being at very early stages of this product our configs change all the time so I feel its better to not lock it in with DB tables.

@wedamija
Copy link
Member

wedamija commented Oct 23, 2025

@wedamija

I'd probably suggest splitting out the new migration into a separate pr so that we can get it through more easily

Thanks for looking! I will move the table to a separate PR and make those schema changes. On using explicit fields instead of a json, it would make the table(s) a lot more confusing and inflexible. The current json object structure is defined like this, and being at very early stages of this product our configs change all the time so I feel its better to not lock it in with DB tables.

The structure there looks pretty regular to me. Should the config have a link to a 1 -> M table that is defined like

ConfigurationFeature
 - preventaiconfiguration_id
 - name
 - enabled
 - sensitivity
 - triggers list[str] # just list the active triggers rather than a dict of true/false. Could also be a M2M to a triggers table, not sure if it's worth that level of normalization though

My experience with unstructured json blobs is that we always end up regretting it

@JerrySentry
Copy link
Contributor Author

@wedamija

My experience with unstructured json blobs is that we always end up regretting it

Yeah I hear you, thing is the configs for this product is so fluid right now, in a week I can see us use a whole different structure. So I'd rather get this config object more matured and when it comes to migrating to a DB schema later we will have to take that pill. While we are still building this pre-GA product I would prefer to be more flexible and not make DB migrations if possible.

Also the structure would almost work except there's a nested "Repos" object in there that overrides the existing configs for the main "Org" object. 😿

@getsantry
Copy link
Contributor

getsantry bot commented Nov 18, 2025

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you remove the label Waiting for: Community, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

@getsantry getsantry bot added the Stale label Nov 18, 2025
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 Stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants