Skip to content

ref(seer): Bill seat-based autofix from Seer project preferences#113369

Draft
srest2021 wants to merge 6 commits intomasterfrom
srest2021/AIML-2754
Draft

ref(seer): Bill seat-based autofix from Seer project preferences#113369
srest2021 wants to merge 6 commits intomasterfrom
srest2021/AIML-2754

Conversation

@srest2021
Copy link
Copy Markdown
Member

Fixes AIML-2754

Determine whether autofix is enabled for a repository (and whether this contributor is billable) by checking Seer project preferences instead of code mappings plus the sentry:autofix_automation_tuning project option. Autofix is enabled if any project has the given repository added to its Seer preferences.

Determine whether autofix is enabled for a repository by checking
Seer project preferences instead of code mappings plus the
`sentry:autofix_automation_tuning` project option. Behind
`organizations:seer-project-settings-read-from-sentry`, read directly
from `SeerProjectRepository`; otherwise bulk-fetch preferences from
Seer and match the repository id, falling back to not incrementing on
`SeerApiError`.

Refs AIML-2754
@linear-code
Copy link
Copy Markdown

linear-code bot commented Apr 17, 2026

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 17, 2026
Comment thread src/sentry/seer/code_review/contributor_seats.py Outdated
@github-actions
Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on 830ae9b in this run:

tests/sentry/seer/code_review/test_contributor_seats.py::ShouldIncrementContributorSeatTest::test_returns_false_when_no_code_review_or_autofix_enabledlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
.venv/lib/python3.13/site-packages/urllib3/connection.py:204: in _new_conn
    sock = connection.create_connection(
.venv/lib/python3.13/site-packages/urllib3/util/connection.py:85: in create_connection
    raise err
.venv/lib/python3.13/site-packages/urllib3/util/connection.py:73: in create_connection
    sock.connect(sa)
E   ConnectionRefusedError: [Errno 111] Connection refused

The above exception was the direct cause of the following exception:
.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:787: in urlopen
    response = self._make_request(
.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:493: in _make_request
    conn.request(
.venv/lib/python3.13/site-packages/urllib3/connection.py:500: in request
    self.endheaders()
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/http/client.py:1331: in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/http/client.py:1091: in _send_output
    self.send(msg)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/http/client.py:1035: in send
    self.connect()
.venv/lib/python3.13/site-packages/urllib3/connection.py:331: in connect
    self.sock = self._new_conn()
.venv/lib/python3.13/site-packages/urllib3/connection.py:219: in _new_conn
    raise NewConnectionError(
E   urllib3.exceptions.NewConnectionError: HTTPConnection(host='127.0.0.1', port=9091): Failed to establish a new connection: [Errno 111] Connection refused

The above exception was the direct cause of the following exception:
tests/sentry/seer/code_review/test_contributor_seats.py:43: in test_returns_false_when_no_code_review_or_autofix_enabled
    result = should_increment_contributor_seat(
src/sentry/seer/code_review/contributor_seats.py:103: in should_increment_contributor_seat
    or not _has_code_review_or_autofix_enabled(organization, repo.id)
src/sentry/seer/code_review/contributor_seats.py:86: in _has_code_review_or_autofix_enabled
    return _is_code_review_enabled_for_repo(repository_id) or _is_autofix_enabled_for_repo(
src/sentry/seer/code_review/contributor_seats.py:64: in _is_autofix_enabled_for_repo
    preferences = bulk_get_project_preferences(organization.id, project_ids)
src/sentry/seer/autofix/utils.py:925: in bulk_get_project_preferences
    response = make_bulk_get_project_preferences_request(
src/sentry/seer/autofix/utils.py:283: in make_bulk_get_project_preferences_request
    return make_signed_seer_api_request(
.venv/lib/python3.13/site-packages/sentry_sdk/tracing_utils.py:916: in sync_wrapper
    result = f(*args, **kwargs)
src/sentry/seer/signed_seer_api.py:168: in make_signed_seer_api_request
    return connection_pool.urlopen(
.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:871: in urlopen
    return self.urlopen(
.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:871: in urlopen
    return self.urlopen(
.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:871: in urlopen
... (6 more lines)
tests/sentry/integrations/github/test_webhook.py::PullRequestEventWebhookTest::test_closedlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/integrations/github/test_webhook.py:1419: in test_closed
    assert response.status_code == 204
E   assert 500 == 204
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/integrations/github/test_webhook.py::PullRequestEventWebhookTest::test_creates_missing_repolog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/integrations/github/test_webhook.py:1233: in test_creates_missing_repo
    self._create_integration_and_send_pull_request_opened_event()
tests/sentry/integrations/github/test_webhook.py:1085: in _create_integration_and_send_pull_request_opened_event
    assert response.status_code == 204
E   assert 500 == 204
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/integrations/github/test_webhook.py::PullRequestEventWebhookTest::test_multiple_orgs_creates_missing_repolog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/integrations/github/test_webhook.py:1285: in test_multiple_orgs_creates_missing_repo
    assert response.status_code == 204
E   assert 500 == 204
E    +  where 500 = <Response status_code=500, "application/json">.status_code

@srest2021
Copy link
Copy Markdown
Member Author

@sentry review

Comment on lines +79 to +82
except (JSONDecodeError, ValidationError, Exception):
sentry_sdk.capture_exception()
return False

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 broad exception handling captures ValidationError and sends it to Sentry, creating noise. A parallel function logs this error instead, which is the preferred pattern for transient API issues.
Severity: LOW

Suggested Fix

Align the error handling with the pattern in bulk_read_preferences. Specifically, catch ValidationError and JSONDecodeError separately from the generic Exception. Use logger.exception() for these expected API-related errors to log them without creating Sentry issues, and reserve sentry_sdk.capture_exception() for truly unexpected exceptions.

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/code_review/contributor_seats.py#L79-L82

Potential issue: The exception handling for `JSONDecodeError` and `ValidationError` in
`_is_autofix_enabled_for_repo` uses `sentry_sdk.capture_exception()`. This is
inconsistent with a parallel function, `bulk_read_preferences`, which handles the same
`ValidationError` by logging it via `logger.exception()`. This inconsistency will cause
transient API issues, such as malformed JSON responses from the Seer API, to be reported
as critical errors in Sentry, creating unnecessary noise. The function correctly falls
back by returning `False`, but the error reporting is overly aggressive for expected API
degradation.

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

Comment on lines +83 to 86
return any(
any(repo.repository_id == repository_id for repo in pref.repositories)
for pref in resolved_preferences
)
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: When the feature flag is off, unresolvable repositories from Seer have repository_id=None, causing the check to silently fail and incorrectly report autofix as disabled, impacting billing.
Severity: MEDIUM

Suggested Fix

In the fallback path, after calling resolve_repository_ids, filter out any repositories from resolved_preferences where repo.repository_id is None. Additionally, consider logging a warning when an unresolvable repository is encountered during this check to make the silent failure visible to operators, similar to the logging in _write_preferences_to_sentry_db.

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/code_review/contributor_seats.py#L83-L86

Potential issue: In the fallback path (when the
`organizations:seer-project-settings-read-from-sentry` feature flag is off), the
function `_is_autofix_enabled_for_repo` can silently fail. If a repository is configured
in Seer but cannot be resolved to a Sentry `Repository` record (due to data
inconsistency or a missing integration), its `repository_id` will remain `None` after
`resolve_repository_ids()` is called. The subsequent check `repo.repository_id ==
repository_id` will evaluate to `False`, causing the function to incorrectly report that
autofix is disabled. This leads to a failure to bill for contributor seats.

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

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.

1 participant