fix: report combined member limit in group AI spend - #27589
Conversation
Docs previewCheck off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here. |
c81b281 to
65beff7
Compare
|
/coder-agents-review |
|
Chat: Review posted | View chat Review history
deep-review v0.9.0 | Round 1 | Last posted: Round 1, 4 findings (1 P2, 2 P3, 1 Note), COMMENT. Review Finding inventoryFinding inventory, PR #27589Findings
Keep-argument for CRF-5 before dropping: three independent copies of the effective-group resolution can drift, and if the reporting copy drifts from the enforcement copy in Round logRound 1Netero-only first pass. 1 P2, 2 P3, 2 Notes. Panel gated by the P2 per the Netero decision gate. Reviewed against 448fe10..65beff7. Orchestrator verification of Netero's findings against the head worktree:
About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
There was a problem hiding this comment.
First-pass review only. These are mechanical findings from a single reviewer; the full panel has not reviewed this PR yet and will run once these are addressed.
The diagnosis behind this PR is right, and the test work is unusually thorough: 8 new table cases at the query level covering plain members, override-replaces-share, zero limits, unbudgeted groups, and cross-group attribution, plus API assertions on every case and a Storybook play that pins the rendered denominator so a regression to spend_limit_micros fails the story. The CTE comments explain intent rather than restating SQL, and the TODO(AIGOV-527) marker is honest about the effective-group duplication.
1 P2, 2 P3, 1 Note. The P2 is the one that matters: the new denominator is scoped to current attribution while the numerator stays historical, so a group with a real budget and no currently attributed members reports a $0 limit against nonzero spend. The reviewer's framing: "the fix trades one wrong denominator for a state that actively signals a breach that is not one." Your own BudgetWithSpend case asserts that state, so it is a decision rather than an oversight, but the frontend consequence looks unintended.
Also worth deciding before merge: the group detail page still shows the per-member limit while the groups list now shows the combined total, so the two pages describe one budget with different numbers.
🤖 This review was automatically generated with Coder Agents.
| -- absent here, so the join below leaves their total null. | ||
| SELECT | ||
| queried_groups.id AS group_id, | ||
| (budget.spend_limit_micros * COALESCE(group_limits.plain_member_count, 0) |
There was a problem hiding this comment.
P3 [CRF-3] spend_limit_micros * plain_member_count is an unchecked bigint multiplication, so a large admin-set group limit makes the whole groups-spend endpoint fail instead of one row being wrong. (Netero)
The only validation on a group budget is
validate:"gte=0"on anint64(codersdk/aibridge.go:422) plus the>= 0CHECK constraint, so a group limit nearint64max is accepted. Two attributed members then overflow bigint, Postgres raisesbigint out of rangefor the statement, andGetOrganizationGroupsAISpendreturns an error for every queried group, not just the offending one. The pre-PR query did no multiplication, so this failure mode is new.
Reviewer marked this unverified: read from the arithmetic and the validation tag, not executed. Reaching it needs an absurd but admin-reachable limit, hence P3. Cheapest fix is a documented upper bound on spend_limit_micros at the API boundary; capping inside the query would hide the misconfiguration.
🤖
There was a problem hiding this comment.
This is a good point 🤔 I guess we could add an upper limit to the group budget and user overrides endpoints. Probably something like $1,000,000/month per person would still be enough for a reasonable number of members of a group. But I would prefer to do this in a separate PR. Wdyt?
There was a problem hiding this comment.
Probably something like $1,000,000/month per person would still be enough for a reasonable number of members of a group. But I would prefer to do this in a separate PR. Wdyt?
👍 It's always easier to raise a limit than to add one. And separate PR is a better call.
65beff7 to
e768a87
Compare
There was a problem hiding this comment.
Potential follow-up: some EXPLAINs on dogfood (unless you've done that already)
There was a problem hiding this comment.
I have not, but we definitely should.
We will probably refactor all these queries in the context of https://linear.app/codercom/issue/AIGOV-527/unify-user-effective-group-resolution-into-a-single-source-of-truth
I'm not a big fan of these big queries with CTEs, but it was the best I could come up with without a big refactor. We have the same logic spread across different queries, and all these CTEs are hard to read.

Problem
The organization groups page showed each group's AI budget as the group's per-member limit, so the total it displayed was effectively group members × group budget. That ignores per-user budget overrides charged to the group, so a group where one member has an override reported a limit that doesn't match what its members can actually spend.
Changes
total_spend_limit_microsto the organization groups AI spend payload, the combined budget of the members attributed to the group, with each member's override replacing their share.nullfor the total when the group has no budget, since its members spend without a cap.Depends on #27568
Note
Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira