Skip to content

Commit 89bdfdc

Browse files
committed
ref(seer-billing): Drop the agentic triage referrer exemption
Credit checks no longer skip runs by referrer. Agentic triage runs are checked like any other until a follow-up marks them by feature instead. Credit checks are behind seer-credit-checks, so nothing changes in production. Refs CW-3061
1 parent ab0d0aa commit 89bdfdc

6 files changed

Lines changed: 9 additions & 110 deletions

File tree

‎src/sentry/seer/autofix/autofix_agent.py‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -832,15 +832,7 @@ def trigger_push_changes(
832832
):
833833
raise PermissionDenied("Code generation is disabled for this organization")
834834

835-
seer_run = SeerRun.objects.filter(
836-
organization_id=group.organization.id, seer_run_state_id=run_id
837-
).first()
838-
check_autofix_billing(
839-
group.organization,
840-
SeerOperation.AUTOFIX_OPEN_PR,
841-
seer_run.referrer if seer_run else None,
842-
is_new_run=False,
843-
)
835+
check_autofix_billing(group.organization, SeerOperation.AUTOFIX_OPEN_PR, is_new_run=False)
844836

845837
client = get_autofix_agent_client(group, user=user)
846838

@@ -859,6 +851,10 @@ def trigger_push_changes(
859851
)
860852
)
861853

854+
seer_run = SeerRun.objects.filter(
855+
organization_id=group.organization.id, seer_run_state_id=run_id
856+
).first()
857+
862858
# Draft when review-request/CI-green is enabled; mark ready once green fires.
863859
client.push_changes(
864860
run_id,

‎src/sentry/seer/autofix/feature/dispatch.py‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ def _org_allows_coding(group: Group) -> bool:
153153
def check_autofix_billing(
154154
organization: Organization,
155155
operation: SeerOperation,
156-
referrer: str | None,
157156
*,
158157
is_new_run: bool,
159158
skip_quota_for_free_cohort: bool = False,
@@ -165,11 +164,9 @@ def check_autofix_billing(
165164
"""
166165
# Check credits for token-billed orgs.
167166
if features.has("organizations:token-billed-seer-enabled", organization):
168-
if (
169-
features.has("organizations:seer-credit-checks", organization)
170-
and referrer != AutofixReferrer.AGENTIC_TRIAGE # Agentic triage runs stay free.
171-
and not has_enough_seer_credits(organization, operation)
172-
):
167+
if features.has(
168+
"organizations:seer-credit-checks", organization
169+
) and not has_enough_seer_credits(organization, operation):
173170
raise SeerOutOfCreditsException()
174171
return False
175172

@@ -200,7 +197,6 @@ def continue_single_run(
200197
check_autofix_billing(
201198
group.organization,
202199
SeerOperation.AUTOFIX_SINGLE_RUN_REPLY,
203-
agent_run.run.referrer,
204200
is_new_run=False,
205201
)
206202

@@ -258,7 +254,6 @@ def trigger_autofix_feature(
258254
uses_quota = check_autofix_billing(
259255
group.organization,
260256
args.step.seer_operation,
261-
args.referrer,
262257
is_new_run=is_new_run,
263258
# Free cohort orgs bypass quota only when called from agentic triage
264259
# (allow_free_cohort=True). Not exposed via the API.

‎src/sentry/seer/endpoints/group_ai_autofix.py‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,16 +165,10 @@ def _parse_autofix_referrer(raw: str | None, request: Request) -> AutofixReferre
165165
return AutofixReferrer.MCP
166166
return AutofixReferrer.GROUP_AUTOFIX_ENDPOINT
167167
try:
168-
referrer = AutofixReferrer(raw)
168+
return AutofixReferrer(raw)
169169
except ValueError:
170170
logger.warning("group_ai_autofix.unknown_referrer", extra={"referrer": raw})
171171
return AutofixReferrer.UNKNOWN
172-
# Agentic triage is not exposed via the API. Don't trust such a referrer since
173-
# it may have token-based billing implications.
174-
if referrer == AutofixReferrer.AGENTIC_TRIAGE:
175-
logger.warning("group_ai_autofix.internal_referrer", extra={"referrer": raw})
176-
return AutofixReferrer.UNKNOWN
177-
return referrer
178172

179173

180174
def _record_user_context_length(request_data: Any) -> None:

‎tests/sentry/seer/autofix/feature/test_dispatch.py‎

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -387,32 +387,6 @@ def test_raises_when_out_of_credits_for_a_continued_step(
387387

388388
MockClient.return_value.continue_feature_run.assert_not_called()
389389

390-
@with_feature(["organizations:seer-credit-checks", "organizations:token-billed-seer-enabled"])
391-
@patch(
392-
"sentry.seer_credit.backend.check_seer_credits",
393-
return_value=CheckSeerCreditsResponse(has_enough_credits=False),
394-
)
395-
def test_agentic_triage_skips_credit_check(self, mock_check_seer_credits: MagicMock) -> None:
396-
fake_run = self.create_seer_run(organization=self.organization, type="feature_run")
397-
398-
with (
399-
patch("sentry.seer.autofix.feature.dispatch.SeerAgentClient") as MockClient,
400-
patch("sentry.seer.autofix.feature.dispatch.quotas"),
401-
):
402-
MockClient.return_value.start_feature_run.return_value = fake_run
403-
404-
run = trigger_autofix_feature(
405-
self.group,
406-
AutofixFeatureArgs(
407-
referrer=AutofixReferrer.AGENTIC_TRIAGE,
408-
step=AutofixStep.ROOT_CAUSE,
409-
step_args=RCAStepArgs(),
410-
),
411-
)
412-
413-
assert run is fake_run
414-
mock_check_seer_credits.assert_not_called()
415-
416390
def test_allows_async_dispatch(self) -> None:
417391
fake_run = self.create_seer_run(organization=self.organization, type="feature_run")
418392

@@ -763,18 +737,3 @@ def test_raises_when_out_of_credits(self, mock_check_seer_credits: MagicMock) ->
763737
)
764738

765739
mock_client_cls.return_value.continue_feature_run.assert_not_called()
766-
767-
@with_feature(["organizations:seer-credit-checks", "organizations:token-billed-seer-enabled"])
768-
@patch(
769-
"sentry.seer_credit.backend.check_seer_credits",
770-
return_value=CheckSeerCreditsResponse(has_enough_credits=False),
771-
)
772-
def test_agentic_triage_run_skips_credit_check(
773-
self, mock_check_seer_credits: MagicMock
774-
) -> None:
775-
"""The run's own referrer decides, not the reply's."""
776-
self.seer_run.update(referrer=AutofixReferrer.AGENTIC_TRIAGE)
777-
778-
self._reply()
779-
780-
mock_check_seer_credits.assert_not_called()

‎tests/sentry/seer/autofix/test_autofix_agent.py‎

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,32 +1685,6 @@ def test_raises_when_out_of_credits(
16851685

16861686
mock_post.assert_not_called()
16871687

1688-
@patch(
1689-
"sentry.seer_credit.backend.check_seer_credits",
1690-
return_value=CheckSeerCreditsResponse(has_enough_credits=False),
1691-
)
1692-
@patch("sentry.seer.agent.client.make_agent_update_request")
1693-
def test_agentic_triage_run_skips_credit_check(
1694-
self, mock_post: MagicMock, mock_check_seer_credits: MagicMock
1695-
) -> None:
1696-
# The push is called with another referrer; the run's own referrer decides.
1697-
self.create_seer_run(
1698-
organization=self.organization,
1699-
seer_run_state_id=123,
1700-
referrer=AutofixReferrer.AGENTIC_TRIAGE.value,
1701-
)
1702-
1703-
self._push(
1704-
mock_post,
1705-
features={
1706-
"organizations:seer-credit-checks": True,
1707-
"organizations:token-billed-seer-enabled": True,
1708-
},
1709-
)
1710-
1711-
mock_check_seer_credits.assert_not_called()
1712-
mock_post.assert_called_once()
1713-
17141688
@patch("sentry.seer.agent.client.make_agent_update_request")
17151689
def test_passes_pr_description_suffix_ready_state_and_author(
17161690
self, mock_post: MagicMock

‎tests/sentry/seer/endpoints/test_group_ai_autofix.py‎

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,25 +1198,6 @@ def test_post_seer_explorer_referrer_is_recognized(
11981198
assert response.status_code == 202, response.data
11991199
assert mock_trigger_explorer.call_args.kwargs["referrer"] == AutofixReferrer.SEER_EXPLORER
12001200

1201-
@patch("sentry.seer.endpoints.group_ai_autofix.trigger_autofix_agent")
1202-
def test_post_agentic_triage_referrer_is_unknown(
1203-
self, mock_trigger_explorer: MagicMock
1204-
) -> None:
1205-
"""Agentic triage runs skip the credit check, so the API can't claim to be one."""
1206-
group = self.create_group()
1207-
run = self.create_seer_run(organization=self.organization, seer_run_state_id=123)
1208-
mock_trigger_explorer.return_value = run
1209-
1210-
self.login_as(user=self.user)
1211-
response = self.client.post(
1212-
self._get_url(group.id),
1213-
data={"step": "root_cause", "referrer": AutofixReferrer.AGENTIC_TRIAGE.value},
1214-
format="json",
1215-
)
1216-
1217-
assert response.status_code == 202, response.data
1218-
assert mock_trigger_explorer.call_args.kwargs["referrer"] == AutofixReferrer.UNKNOWN
1219-
12201201
@patch("sentry.seer.endpoints.group_ai_autofix.trigger_autofix_agent")
12211202
def test_stopping_point(self, mock_trigger_explorer: MagicMock) -> None:
12221203
"""Stopping point forces the step to be root_cause"""

0 commit comments

Comments
 (0)