Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
cbdde3a
feat(seer): Gate agent chat messages on Seer credits
srest2021 Nov 6, 2026
c247328
feat(seer): Check Seer credits before Autofix steps and Open PR
srest2021 Nov 6, 2026
12cebaf
feat(seer): Handle SeerOutOfCreditsException at Autofix callers
srest2021 Nov 6, 2026
e2855ba
ref(seer): Keep the token-billed check inside can_run_seer_operation
srest2021 Nov 6, 2026
33f5287
Merge branch 'srest2021/CW-3061-agent-chat' into srest2021/CW-3061-au…
srest2021 Nov 6, 2026
d70d6cb
ref(seer): Split has_enough_seer_credits out of can_run_seer_operation
srest2021 Nov 6, 2026
56c7833
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
58a1b63
ref(seer): Combine the credit check flag conditions
srest2021 Nov 6, 2026
8856cd4
Merge branch 'srest2021/CW-3061-agent-chat' into srest2021/CW-3061-au…
srest2021 Nov 6, 2026
da71d66
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
c49f7ed
Merge remote-tracking branch 'origin/master' into srest2021/CW-3061-a…
srest2021 Nov 6, 2026
8e5ca8f
Merge branch 'srest2021/CW-3061-agent-chat' into srest2021/CW-3061-au…
srest2021 Nov 6, 2026
d2ef51a
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
0ab3b10
feat(seer): Check Seer credits before replying to an Autofix single run
srest2021 Nov 6, 2026
5cecd74
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
8552e64
feat(seer): Return 402 when a single run reply is out of credits
srest2021 Nov 6, 2026
69f5488
Merge remote-tracking branch 'origin/master' into srest2021/CW-3061-a…
srest2021 Nov 6, 2026
0f648e8
Merge branch 'srest2021/CW-3061-agent-chat' into srest2021/CW-3061-au…
srest2021 Nov 6, 2026
a3dae0d
ref(seer): Check the credit check flag first
srest2021 Nov 6, 2026
f738ddf
Merge branch 'srest2021/CW-3061-agent-chat' into srest2021/CW-3061-au…
srest2021 Nov 6, 2026
bdac9ed
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
94b979c
test(seer): Align credit check tests with their files
srest2021 Nov 6, 2026
302de4f
Merge branch 'srest2021/CW-3061-agent-chat' into srest2021/CW-3061-au…
srest2021 Nov 6, 2026
eae815f
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
0ec42da
Merge remote-tracking branch 'origin/master' into srest2021/CW-3061-a…
srest2021 Nov 6, 2026
6f7cb6f
ref(seer): Leave single run replies out of credit gating
srest2021 Nov 6, 2026
52c156d
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
9248e4c
Revert "feat(seer): Return 402 when a single run reply is out of cred…
srest2021 Nov 6, 2026
e35a52c
cleaning up diff
srest2021 Nov 6, 2026
99eb2ac
fix(seer): Don't let API callers claim the agentic triage referrer
srest2021 Nov 6, 2026
b8824e4
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
e8f415e
ref(seer): Clarify why the endpoint ignores the agentic triage referrer
srest2021 Nov 6, 2026
a1a9540
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
396779d
feat(seer): Check Seer credits before replying to an Autofix single run
srest2021 Nov 6, 2026
783742c
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
3c0d97b
feat(seer): Return 402 when a single run reply is out of credits
srest2021 Nov 6, 2026
90c5db5
cleaning up diff
srest2021 Nov 6, 2026
ff4faae
ref(seer): Restore the billing comment in trigger_autofix_agent
srest2021 Nov 6, 2026
f7c9960
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
2b31435
fix comment
srest2021 Nov 6, 2026
b86335d
cleaning up
srest2021 Nov 6, 2026
245aca9
fix ordering
srest2021 Nov 6, 2026
80bba23
Restore comment
srest2021 Nov 6, 2026
914a60c
ref(seer): Keep all billing skip rules in check_autofix_billing
srest2021 Nov 6, 2026
df89e79
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
28e5990
refactor billing helper
srest2021 Nov 6, 2026
766bc10
move logs out of helper
srest2021 Nov 6, 2026
6c0043d
small test fix:
srest2021 Nov 6, 2026
1c7c190
cleaning up tests
srest2021 Nov 6, 2026
a502768
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 6, 2026
8a11dd1
feat(seer-billing): Check Seer credits before queueing UI PR feedback
srest2021 Nov 6, 2026
c787f4d
fix(seer-billing): Don't pause PR iteration when a retry is out of cr…
srest2021 Nov 6, 2026
4e289d3
Merge remote-tracking branch 'origin/master' into srest2021/CW-3061-a…
srest2021 Nov 7, 2026
21285b1
Merge branch 'srest2021/CW-3061-autofix-gating' into srest2021/CW-306…
srest2021 Nov 7, 2026
451f3bd
ref(seer-billing): Pass organization to check_autofix_billing
sentry-junior[bot] Nov 9, 2026
ab0d0aa
ref(seer-billing): Check Autofix billing only in dispatch
srest2021 Nov 9, 2026
f271a38
improve pr iteration behavior
srest2021 Nov 9, 2026
89bdfdc
ref(seer-billing): Drop the agentic triage referrer exemption
srest2021 Nov 9, 2026
8888ed6
Merge remote-tracking branch 'origin/srest2021/CW-3061-autofix-gating…
srest2021 Nov 10, 2026
5971753
ref(seer-billing): Add codes to Seer out-of-credits and out-of-quota …
sentry-junior[bot] Nov 10, 2026
5e7ccfc
ref(seer-billing): Pass the Seer feature to credit checks
srest2021 Nov 10, 2026
1a78b12
ref(seer-billing): Pick the credit check feature from the operation
srest2021 Nov 10, 2026
66d497e
Merge remote-tracking branch 'origin/srest2021/CW-3061-autofix-gating…
srest2021 Nov 10, 2026
de5b2ab
ref(seer-billing): Pass the Seer feature to the PR feedback credit check
srest2021 Nov 10, 2026
d102d4a
Merge branch 'master' into srest2021/CW-3061-autofix-out-of-credits
srest2021 Nov 10, 2026
6df4d36
ref(seer-billing): Share the Seer out-of-credits messages
srest2021 Nov 10, 2026
f6ca995
Merge remote-tracking branch 'origin/srest2021/CW-3061-autofix-out-of…
srest2021 Nov 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sentry/seer/autofix/issue_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
IssueSummaryHidden,
IssueSummarySelfHosted,
NoSeerQuotaException,
SeerOutOfCreditsException,
)
from sentry.seer.autofix.steps import AutofixStep
from sentry.seer.autofix.utils import (
Expand Down Expand Up @@ -204,7 +205,7 @@ def _trigger_autofix_task(
data={"referrer": referrer.value},
send_notification=False,
)
except NoSeerQuotaException:
except (NoSeerQuotaException, SeerOutOfCreditsException):
pass

if run and SeerAutofixOperator.has_access(organization=group.project.organization):
Expand Down
29 changes: 23 additions & 6 deletions src/sentry/seer/autofix/on_completion_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
)
from sentry.seer.autofix.coding_agent import IntegrationNotFound
from sentry.seer.autofix.constants import AutofixReferrer
from sentry.seer.autofix.exceptions import SeerOutOfCreditsException
from sentry.seer.autofix.feature.models import FEATURE_ID as AUTOFIX_FEATURE_ID
from sentry.seer.autofix.feature.models import LEGACY_FEATURE_ID as LEGACY_AUTOFIX_FEATURE_ID
from sentry.seer.autofix.pr_iteration.completion import (
Expand Down Expand Up @@ -786,12 +787,22 @@ def _maybe_continue_pipeline(
"iteration_index": get_latest_iteration_index(state),
},
)
trigger_autofix_agent(
group=group,
step=next_step,
referrer=referrer,
run_id=run_id,
)
try:
trigger_autofix_agent(
group=group,
step=next_step,
referrer=referrer,
run_id=run_id,
)
except SeerOutOfCreditsException:
logger.info(
"autofix.on_completion_hook.next_step_out_of_credits",
extra={
"run_id": run_id,
"organization_id": organization.id,
"next_step": next_step,
},
)

@classmethod
def _push_changes(
Expand Down Expand Up @@ -841,6 +852,12 @@ def _push_changes(
state=state,
verify_content=True,
)
except SeerOutOfCreditsException:
logger.info(
"autofix.on_completion_hook.push_changes_out_of_credits",
extra={"run_id": run_id, "organization_id": group.organization.id},
)
return False
except Exception:
logger.exception(
"autofix.on_completion_hook.push_changes_failed",
Expand Down
16 changes: 16 additions & 0 deletions src/sentry/seer/autofix/pr_iteration/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from sentry.seer.autofix.autofix_agent import trigger_push_changes
from sentry.seer.autofix.commit_author import SeerCommitAuthor, parse_commit_author
from sentry.seer.autofix.constants import AutofixReferrer
from sentry.seer.autofix.exceptions import SeerOutOfCreditsException
from sentry.seer.autofix.feature.dispatch import AutofixFeatureArgs, trigger_autofix_feature
from sentry.seer.autofix.feature.models import PrIterationStepArgs
from sentry.seer.autofix.pr_iteration.current_iteration import triggered_iteration_id
Expand Down Expand Up @@ -489,6 +490,16 @@ def _push_iteration_changes(
# Never overwrite commits on the PR that Seer didn't push.
allow_force_push=False,
)
except SeerOutOfCreditsException:
log_ctx.info("autofix.pr_iteration.push", outcome="failed", reason="out_of_credits")
# TODO(CW-3066): Resume after a top-up. Until then nothing retries the push, so pause
# the run so that new feedback is refused.
pause_pr_iteration(
run_id=run_id,
organization_id=group.organization.id,
reason=PauseReason.RUN_ERRORED,
)
return False
except Exception as e:
log_ctx.error(
"autofix.pr_iteration.push",
Expand Down Expand Up @@ -572,6 +583,11 @@ def _retry_push_on_moved_branch(
),
),
)
except SeerOutOfCreditsException:
log_ctx.info("autofix.pr_iteration.push", outcome="not_pushed", reason="out_of_credits")
# TODO(CW-3066): Resume after a top-up. Until then nothing retries this, so end the
# iteration as errored, and the caller pauses the run and records the outcome.
return PrIterationOutcome.PR_CREATION_ERRORED
except Exception as e:
# End the iteration as errored, so the caller pauses the run and records the outcome.
log_ctx.error(
Expand Down
24 changes: 21 additions & 3 deletions src/sentry/seer/autofix/pr_iteration/manual_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@
from sentry.seer.autofix.pr_iteration.feedback_sources.base import ConsumeTriggerSource
from sentry.seer.autofix.pr_iteration.pause import PauseReason, get_pause_reason
from sentry.seer.autofix.pr_iteration.queue import enqueue_autofix_feedback
from sentry.seer.endpoints.utils import SEER_PERMISSION_DENIED
from sentry.seer.credits import SeerFeature, SeerOperation, can_run_seer_operation
from sentry.seer.endpoints.utils import (
SEER_AUTOFIX_CONTINUE_OUT_OF_CREDITS_DETAIL,
SEER_OUT_OF_CREDITS_CODE,
SEER_PERMISSION_DENIED,
)
from sentry.seer.models import SeerPermissionError
from sentry.tasks.seer.pr_iteration import trigger_consume_pr_iteration_feedback
from sentry.users.services.user.service import user_service
Expand All @@ -52,8 +57,9 @@ def handle_ui_feedback(

Returns None once the feedback has been handed off, or the error response
the endpoint should send back when the request cannot be acted on: 400 when
the feedback is empty or the run has no pull request yet, and 409 when
iteration on the run is paused. Raises ``PermissionDenied`` when Seer
the feedback is empty or the run has no pull request yet, 409 when
iteration on the run is paused, and 402 when the organization doesn't have
enough Seer credits. Raises ``PermissionDenied`` when Seer
refuses to show us the run.
"""
if not user_feedback:
Expand All @@ -80,6 +86,18 @@ def handle_ui_feedback(
status=status.HTTP_409_CONFLICT,
)

# Check credits here, since the queued feedback is consumed after this request returns.
if not can_run_seer_operation(
group.organization, SeerFeature.AUTOFIX, SeerOperation.AUTOFIX_PR_ITERATION
):
return Response(
{
"detail": SEER_AUTOFIX_CONTINUE_OUT_OF_CREDITS_DETAIL,
"code": SEER_OUT_OF_CREDITS_CODE,
},
status=status.HTTP_402_PAYMENT_REQUIRED,
)

serialized_users = user_service.serialize_many(
filter={"user_ids": [user_id]},
)
Expand Down
3 changes: 3 additions & 0 deletions src/sentry/seer/endpoints/admin_autofix_retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
trigger_autofix_agent,
)
from sentry.seer.autofix.constants import AutofixReferrer
from sentry.seer.autofix.exceptions import SeerOutOfCreditsException
from sentry.seer.autofix.feature.models import FEATURE_ID, LEGACY_FEATURE_ID
from sentry.seer.autofix.on_completion_hook import PIPELINE_ORDER
from sentry.seer.models import SeerRun
Expand Down Expand Up @@ -120,6 +121,8 @@ def _retry_run(organization: Organization, run_id: int) -> dict[str, Any]:
run_id=run_id,
insert_index=insert_index,
)
except SeerOutOfCreditsException:
return _skipped(run_id, "Organization does not have enough Seer credits")
except Exception:
logger.exception(
"autofix.admin_retry.trigger_failed",
Expand Down
6 changes: 5 additions & 1 deletion src/sentry/seer/endpoints/admin_autofix_single_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from sentry.models.organization import Organization
from sentry.seer.autofix.autofix_agent import trigger_autofix_agent
from sentry.seer.autofix.constants import AutofixReferrer
from sentry.seer.autofix.exceptions import NoSeerQuotaException
from sentry.seer.autofix.exceptions import NoSeerQuotaException, SeerOutOfCreditsException
from sentry.seer.autofix.steps import AutofixStep
from sentry.seer.autofix.utils import get_automation_handoff
from sentry.seer.models import SeerPermissionError
Expand Down Expand Up @@ -75,6 +75,10 @@ def post(self, request: Request) -> Response:
)
except NoSeerQuotaException:
return Response({"detail": "Organization has no Seer quota"}, status=400)
except SeerOutOfCreditsException:
return Response(
{"detail": "Organization does not have enough Seer credits"}, status=400
)
except SeerPermissionError as e:
return Response({"detail": e.message}, status=400)

Expand Down
35 changes: 32 additions & 3 deletions src/sentry/seer/endpoints/group_ai_autofix.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
)
from sentry.seer.autofix.commit_author import commit_author_for_user
from sentry.seer.autofix.constants import PUBLISH_SCOPES, AutofixReferrer
from sentry.seer.autofix.exceptions import NoSeerQuotaException
from sentry.seer.autofix.exceptions import NoSeerQuotaException, SeerOutOfCreditsException
from sentry.seer.autofix.feature.dispatch import project_stopping_point, should_use_single_run
from sentry.seer.autofix.feature.models import FEATURE_ID as AUTOFIX_FEATURE_ID
from sentry.seer.autofix.feature.models import SINGLE_RUN_FEATURE_ID
Expand All @@ -81,7 +81,15 @@
from sentry.seer.endpoints.organization_seer_onboarding_check import (
has_supported_scm_integration,
)
from sentry.seer.endpoints.utils import SEER_PERMISSION_DENIED, get_seer_run, resolve_seer_run
from sentry.seer.endpoints.utils import (
SEER_AUTOFIX_CONTINUE_OUT_OF_CREDITS_DETAIL,
SEER_AUTOFIX_START_OUT_OF_CREDITS_DETAIL,
SEER_OUT_OF_CREDITS_CODE,
SEER_OUT_OF_QUOTA_CODE,
SEER_PERMISSION_DENIED,
get_seer_run,
resolve_seer_run,
)
from sentry.seer.models import UNKNOWN_RUN_ID_FOR_GROUP, SeerPermissionError
from sentry.types.activity import ActivityType
from sentry.types.ratelimit import RateLimit, RateLimitCategory
Expand Down Expand Up @@ -455,6 +463,14 @@ def post(
),
user=acting_user(request.user),
)
except SeerOutOfCreditsException:
return Response(
{
"detail": SEER_AUTOFIX_CONTINUE_OUT_OF_CREDITS_DETAIL,
"code": SEER_OUT_OF_CREDITS_CODE,
},
status=status.HTTP_402_PAYMENT_REQUIRED,
)
except SeerPermissionError:
return Response(status=status.HTTP_404_NOT_FOUND)

Expand Down Expand Up @@ -583,7 +599,20 @@ def post(
)
except NoSeerQuotaException:
return Response(
"No budget for Seer Autofix.", status=status.HTTP_402_PAYMENT_REQUIRED
{"detail": "No budget for Seer Autofix.", "code": SEER_OUT_OF_QUOTA_CODE},
status=status.HTTP_402_PAYMENT_REQUIRED,
)
except SeerOutOfCreditsException:
return Response(
{
"detail": (
SEER_AUTOFIX_START_OUT_OF_CREDITS_DETAIL
if resolved_run_id is None
else SEER_AUTOFIX_CONTINUE_OUT_OF_CREDITS_DETAIL
),
"code": SEER_OUT_OF_CREDITS_CODE,
},
status=status.HTTP_402_PAYMENT_REQUIRED,
)
Comment thread
srest2021 marked this conversation as resolved.
Resolved
except SeerPermissionError as e:
if _is_unknown_run_id_error(e):
Expand Down
17 changes: 13 additions & 4 deletions src/sentry/seer/endpoints/organization_seer_agent_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,18 @@
PUBLISH_SCOPES,
AutofixReferrer,
)
from sentry.seer.autofix.exceptions import SeerOutOfCreditsException
from sentry.seer.autofix.feature.dispatch import continue_single_run
from sentry.seer.autofix.feature.models import SINGLE_RUN_FEATURE_ID
from sentry.seer.autofix.single_run import is_single_run
from sentry.seer.credits import SeerFeature, SeerOperation, can_run_seer_operation
from sentry.seer.endpoints.group_ai_autofix import RUN_IN_FLIGHT_CODE
from sentry.seer.endpoints.utils import ResolvedSeerRun, resolve_seer_run
from sentry.seer.endpoints.utils import (
SEER_AGENT_CHAT_OUT_OF_CREDITS_DETAIL,
SEER_OUT_OF_CREDITS_CODE,
ResolvedSeerRun,
resolve_seer_run,
)
from sentry.seer.models import SeerApiError, SeerPermissionError
from sentry.seer.models.run import SeerAgentRun
from sentry.seer.seer_setup import has_seer_access_with_detail
Expand Down Expand Up @@ -268,6 +274,11 @@ def _maybe_reply_to_single_run(
can_publish=request.access.has_any_project_scope(project, PUBLISH_SCOPES),
referrer=AutofixReferrer.SEER_EXPLORER,
)
except SeerOutOfCreditsException:
return Response(
{"detail": SEER_AGENT_CHAT_OUT_OF_CREDITS_DETAIL, "code": SEER_OUT_OF_CREDITS_CODE},
status=402,
)
except SeerApiError as e:
if e.status == 404:
return Response({"session": None}, status=404)
Expand Down Expand Up @@ -442,9 +453,7 @@ def post(
organization, SeerFeature.EXPLORER, SeerOperation.AGENT_CHAT_MESSAGE
):
return Response(
{
"detail": "Your organization does not have enough credits left for you to message Seer."
},
{"detail": SEER_AGENT_CHAT_OUT_OF_CREDITS_DETAIL, "code": SEER_OUT_OF_CREDITS_CODE},
status=402,
)

Expand Down
14 changes: 14 additions & 0 deletions src/sentry/seer/endpoints/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@

SEER_PERMISSION_DENIED = "You are not authorized to perform this action"

# Let API clients like the CLI and MCP tell billing 402s apart without parsing detail text.
SEER_OUT_OF_CREDITS_CODE = "seer_out_of_credits"
SEER_OUT_OF_QUOTA_CODE = "seer_out_of_quota"

SEER_AUTOFIX_START_OUT_OF_CREDITS_DETAIL = (
"Your organization does not have enough credits to start an Autofix run."
)
SEER_AUTOFIX_CONTINUE_OUT_OF_CREDITS_DETAIL = (
"Your organization does not have enough credits to continue your Autofix run."
)
SEER_AGENT_CHAT_OUT_OF_CREDITS_DETAIL = (
"Your organization does not have enough credits left for you to message Seer."
)


class OrganizationTraceExplorerAIPermission(OrganizationPermission):
scope_map = {
Expand Down
12 changes: 11 additions & 1 deletion src/sentry/seer/entrypoints/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def trigger_autofix_agent(
trigger_autofix_agent,
trigger_push_changes,
)
from sentry.seer.autofix.exceptions import NoSeerQuotaException
from sentry.seer.autofix.exceptions import NoSeerQuotaException, SeerOutOfCreditsException
from sentry.seer.autofix.feature.models import FEATURE_ID as AUTOFIX_FEATURE_ID
from sentry.seer.autofix.steps import AutofixStep

Expand Down Expand Up @@ -313,6 +313,16 @@ def trigger_autofix_agent(
self.entrypoint.on_trigger_autofix_error(error=error)
lifecycle.record_failure(failure_reason=error)
return
except SeerOutOfCreditsException:

@srest2021 srest2021 Nov 6, 2026 •

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.

Coordination note for the Slack tickets.

This PR: If someone clicks an old Slack Autofix button after the org runs out of credits, trigger_autofix_agent / trigger_push_changes raise SeerOutOfCreditsException. The operator catches it in SeerAutofixOperator.trigger_autofix_agent and replies through on_trigger_autofix_error, so the thread shows the generic "Seer had some trouble..." error message.

#130271 adds SeerAutofixOutOfCredits, and #130271 says the click reply is CW-2325. But #129975 doesn't touch the operator, so nothing sends it on a click yet. Once both are in, could CW-2325 (or a follow-up) send SeerAutofixOutOfCredits(started=run_id is not None) from that except instead of the generic error? Then a click and a run that stops part way show the same message.

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.

I've cut out CW-3080 to track the work to post richer replies for this case. Thanks!

with SeerOperatorEventLifecycleMetric(
interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_AUTOFIX_ERROR,
entrypoint_key=self.entrypoint.key,
).capture():
self.entrypoint.on_trigger_autofix_error(
error="Your organization does not have enough Seer credits"
)
lifecycle.record_halt(halt_reason="not_enough_seer_credits")
return
except Exception as e:
with SeerOperatorEventLifecycleMetric(
interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_AUTOFIX_ERROR,
Expand Down
18 changes: 13 additions & 5 deletions src/sentry/tasks/seer/pr_iteration.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from sentry.seer.autofix.autofix_agent import trigger_autofix_agent
from sentry.seer.autofix.commit_author import commit_author_for_feedback
from sentry.seer.autofix.constants import AutofixReferrer
from sentry.seer.autofix.exceptions import SeerOutOfCreditsException
from sentry.seer.autofix.pr_iteration.bot_identity import bot_logins_for_feedback
from sentry.seer.autofix.pr_iteration.cap_exhausted import assign_user_for_exhausted_cap
from sentry.seer.autofix.pr_iteration.constants import PR_ITERATION_PROVIDER
Expand Down Expand Up @@ -840,7 +841,11 @@ def _drain_queued_autofix_feedback(
commit_author=commit_author_for_feedback(feedback_items, organization_id),
iteration_id=iteration_id,
)
except (PrIterationNoPullRequestException, SeerPermissionError) as error:
except (
PrIterationNoPullRequestException,
SeerPermissionError,
SeerOutOfCreditsException,
) as error:
log_ctx.info(
"autofix.pr_iteration.consume_feedback.trigger_agent",
outcome="skipped",
Expand All @@ -849,10 +854,13 @@ def _drain_queued_autofix_feedback(
trigger_id=trigger_id,
trigger_source=trigger_source,
)
_record_drain_outcome(
"skipped_permission" if isinstance(error, SeerPermissionError) else "skipped_no_pr",
trigger_source,
)
if isinstance(error, SeerPermissionError):
drain_outcome = "skipped_permission"
elif isinstance(error, SeerOutOfCreditsException):
drain_outcome = "skipped_out_of_credits"
else:
drain_outcome = "skipped_no_pr"
_record_drain_outcome(drain_outcome, trigger_source)
# The drain popped the queue, so this iteration will never run.
_discard_iteration(log_ctx, run_id, organization_id, iteration_id)
return
Expand Down
Loading
Loading