feat: add ai-budget-exceeded PR label when cost cap is hit - #60
Conversation
When the per-ticket AI session cost cap is exceeded, the bot now applies an `ai-budget-exceeded` label to all open PRs for the ticket. Previously the only signal was a generic `blocked` Jira label that PR reviewers were unlikely to see. The label reflects live state: re-applied each scan cycle if removed while the condition holds, and automatically cleared when the bot successfully runs again (e.g., after the cap is raised). It participates in the existing PR validation label mutual exclusivity group alongside `ai-validation-failed` and `ai-nonzero-exit`. The label defaults to "ai-budget-exceeded" when not configured — the only PR validation label with a non-empty default. Set `cost_cap_exceeded: ""` in `pr_validation_labels` to disable. The Jira `blocked` label is no longer set for cost-cap-exceeded (it is still set for general pipeline failures). Assisted-by: Claude Opus 4.6 (1M) <noreply@anthropic.com>
WalkthroughAdds configurable cost-cap PR labels, defaults the label when omitted, integrates label application into cost-cap failures, and tests configuration, mutual exclusivity, repository handling, disabled behavior, and lookup errors. ChangesCost-cap PR labeling
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant TicketPipeline
participant CostCapLabeler
participant GitHub
TicketPipeline->>CostCapLabeler: handle cost-cap state
CostCapLabeler->>GitHub: find open PRs by branch heads
GitHub-->>CostCapLabeler: return matching PRs
CostCapLabeler->>GitHub: apply or remove cost-cap label
🚥 Pre-merge checks | ✅ 12 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (12 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
executor/pipeline.go (1)
136-143: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAdd a symmetric clear-on-resolved call here too.
For consistency with the fix proposed for
executor/feedback.go, addp.applyCostCapPRLabel(logger, job.TicketKey, settings, false)right after the cost-cap check passes, so any pre-existing stale cost-cap label (e.g., from a recovered/retried ticket that already has a PR) is proactively cleared rather than relying on downstream label logic to run. See the consolidated comment for full rationale.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@executor/pipeline.go` around lines 136 - 143, After the cost-cap check in the pipeline flow, invoke applyCostCapPRLabel with job.TicketKey, settings, and false so stale cost-cap labels are cleared when the ticket is within the cap; leave the existing exceeded-path call and return behavior unchanged.executor/feedback.go (1)
49-56: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winCost-cap PR label is never proactively cleared when the condition resolves — only two files, one missing call.
Both cost-cap gates call
applyCostCapPRLabel(..., true)when exceeded, but neither calls it withfalseonce the check passes. Clearing today depends entirely onvl.All()being swept by later validation-label logic (Step 16a in pipeline.go / Step 17b in feedback.go) — logic that has early-return paths bypassing it (concretely,feedback.go's "no new comments or CI failures" return at lines 118-121). Result: a PR can keep theai-budget-exceededlabel indefinitely after the ticket is no longer over budget, contradicting the documented "removed when the bot successfully runs again" behavior.
executor/feedback.go#L49-L56: addp.applyCostCapPRLabel(logger, job.TicketKey, settings, false)immediately after theticketCostCapExceededcheck passes, before any early returns (e.g., the no-new-comments branch at lines 118-121).executor/pipeline.go#L136-L143: add the samep.applyCostCapPRLabel(logger, job.TicketKey, settings, false)call after the check passes, for symmetry and to cover recovered/retried tickets that may already have a PR.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@executor/feedback.go` around lines 49 - 56, Cost-cap labels are not proactively removed after a ticket returns within budget. In executor/feedback.go lines 49-56, call applyCostCapPRLabel with false immediately after ticketCostCapExceeded passes, before any early return; make the same change in executor/pipeline.go lines 136-143 after its cost-cap check passes to cover both execution paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@executor/labels_test.go`:
- Around line 853-862: Update the “PR lookup errors are swallowed” subtest to
assert that ApplyCostCapPRLabel does not invoke any label add or remove
operation when GetPRForBranchFunc returns an error. Use the test dependency’s
existing label-operation tracking or mocks, and keep the error scenario setup
unchanged.
---
Outside diff comments:
In `@executor/feedback.go`:
- Around line 49-56: Cost-cap labels are not proactively removed after a ticket
returns within budget. In executor/feedback.go lines 49-56, call
applyCostCapPRLabel with false immediately after ticketCostCapExceeded passes,
before any early return; make the same change in executor/pipeline.go lines
136-143 after its cost-cap check passes to cover both execution paths.
In `@executor/pipeline.go`:
- Around line 136-143: After the cost-cap check in the pipeline flow, invoke
applyCostCapPRLabel with job.TicketKey, settings, and false so stale cost-cap
labels are cleared when the ticket is within the cap; leave the existing
exceeded-path call and return behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 2648ddbe-40e9-42b5-95f5-410bc0bfd54a
📒 Files selected for processing (11)
AGENTS.mdconfig.example.yamlexecutor/export_test.goexecutor/feedback.goexecutor/labels.goexecutor/labels_test.goexecutor/pipeline.gomodels/config.gomodels/config_test.goprojectresolver/resolver.goprojectresolver/resolver_test.go
Add applyCostCapPRLabel(..., false) immediately after the cost cap check passes in both pipeline.go and feedback.go. Without this, a stale ai-budget-exceeded label could persist on PRs when the feedback path takes an early return (e.g., no new comments) before the downstream validation label logic runs. Also add assertions to the "PR lookup errors are swallowed" test to verify no label operations occur when the PR lookup fails. Assisted-by: Claude Opus 4.6 (1M) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@executor/pipeline.go`:
- Around line 141-144: The cost-cap error path still receives the generic
blocked Jira label through executeNewTicket’s deferred handleFailure. Update
handleFailure to skip applying settings.FailureLabels.Blocked specifically for
errTicketCostCapExceeded, while preserving blocked labeling for all other
failures; retain the PR label behavior in applyCostCapPRLabel and add a
regression test covering the new-ticket path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 91e3c87a-86da-4751-b9ef-d853ed6c7861
📒 Files selected for processing (3)
executor/feedback.goexecutor/labels_test.goexecutor/pipeline.go
Summary
ai-budget-exceededGitHub PR label to all open PRs for the ticket, making the bot's state visible where reviewers are actually lookingai-validation-failed/ai-nonzero-exitwhen set, and vice versa)"ai-budget-exceeded"when not configured; setcost_cap_exceeded: ""to disableblockedlabel for cost-cap-exceeded (still set for general pipeline failures)Motivation: PR osac-project/fulfillment-service#917 — a reviewer asked the bot to fix a failing test, but the bot silently stopped because the $20 per-ticket cost cap was exhausted. The only signal was a Jira label the reviewer never saw.
Test plan
go test -race ./models/...—CostCapLabel(),All(), YAML loading with nil/empty/set pointergo test -race ./projectresolver/...— default applied when nil, custom value passthrough, explicit empty disablesgo test -race ./executor/...—applyCostCapPRLabel(apply, remove, disabled no-op, multi-repo, error swallowing), mutual exclusivity with all 3 labelsmake lint— clean (only pre-existing gosec warnings)Assisted-by: Claude noreply@anthropic.com
Packages affected
ai-budget-exceededby default, configurable) across all configured repositories. Applies/removes/reattaches the label on each bot run while the condition remains active, enforces mutual exclusivity withai-validation-failedandai-nonzero-exit, and changes cost-cap violations to no longer set the Jirablockedlabel. Adds/expands test coverage for multi-repo behavior, configuration disablement, label removal, lookup-error resilience (best-effort), and exclusivity behavior.CostCapExceeded(optional) and helpers to default toai-budget-exceededand allow disabling viacost_cap_exceeded: "". Updates aggregation of validation labels to include the new cost-cap label.ai-budget-exceededwhen not explicitly configured.Execution pipeline
blockedlabel for that condition.ai-budget-exceededvs.ai-validation-failed/ai-nonzero-exit).Configuration and documentation
pr_validation_labels.cost_cap_exceeded.pr_validation_labelsfeature, including the new cost-cap label’s defaulting, disablement behavior, mutual exclusivity, and how it should be refreshed/cleared based on live cost-cap state.