Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: report combined member limit in group AI spend - #27589

Merged
ssncferreira merged 4 commits into
mainfrom
ssncf/group-ai-total-spend-limit
Jul 29, 2026
Merged

fix: report combined member limit in group AI spend#27589
ssncferreira merged 4 commits into
mainfrom
ssncf/group-ai-total-spend-limit

Conversation

@ssncferreira

@ssncferreira ssncferreira commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

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

  • Add total_spend_limit_micros to the organization groups AI spend payload, the combined budget of the members attributed to the group, with each member's override replacing their share.
  • Return null for the total when the group has no budget, since its members spend without a cap.
  • Both the organization groups and single group spend endpoints report the new field, as they share the same query.
  • Use the total as the denominator on the groups page AI budget column.

Depends on #27568

Note

Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Docs preview

Check 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.

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@ssncferreira
ssncferreira force-pushed the ssncf/group-ai-total-spend-limit branch from c81b281 to 65beff7 Compare July 28, 2026 14:48
@ssncferreira

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-07-28 14:55 UTC by @ssncferreira
Spend: $3.52 / $100.00

Review history
  • R1 (2026-07-28), 1 Note, 1 P2, 2 P3, COMMENT. Review

deep-review v0.9.0 | Round 1 | 448fe10..65beff7

Last posted: Round 1, 4 findings (1 P2, 2 P3, 1 Note), COMMENT. Review

Finding inventory

Finding inventory, PR #27589

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Open coderd/database/queries/aicostcontrol.sql:193 Budgeted group with no currently attributed members reports total 0 while historical spend stands, rendering a false "exceeded" R1 Netero Yes
CRF-2 P3 Open codersdk/aibridge.go:92 Doc comment states the wrong null condition and ships in the public API schema R1 Netero Yes
CRF-3 P3 Open coderd/database/queries/aicostcontrol.sql:193 Unchecked bigint multiplication can fail the whole endpoint, not one row R1 Netero Yes
CRF-4 Note Open codersdk/aibridge.go:94 Single-group endpoint now returns the field; group detail page still shows the per-member limit, so two pages describe one budget with different numbers R1 Netero Yes
CRF-5 Note Dropped by orchestrator (already ticketed in-code as TODO(AIGOV-527); Netero verified the three copies agree today) coderd/database/queries/aicostcontrol.sql:147 Third copy of the effective-group CTE in this file R1 Netero No

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 GetOverBudgetUsersPerGroup, the page would show a budget the enforcer does not apply. Dropped anyway because Netero compared the new copy's ordering, joins, and organizations.deleted = false filter against GetHighestGroupAIBudgetByUser and found them identical, the author added the TODO(AIGOV-527) marker in this diff, and the ticket exists. Posting it would restate a comment the author already wrote.

Round log

Round 1

Netero-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:

  • CRF-1: confirmed. group_totals (sql:187-195) inner-joins group_ai_budgets and multiplies by plain_member_count derived from current attribution, while group_spend (sql:197-205) sums ai_user_daily_spend by effective_group_id, which is historical. The PR's own BudgetWithSpend case (querier_test.go:12854-12861) asserts limit 1_000_000, spend 250, no members, wantTotalLimit: {0, true}. Render path confirmed: GroupsPageView.tsx:221 passes the total as spendLimit, and getSeverity(used, 0) returns "exceeded" for used > 0 (site/src/utils/budget.ts:11-13).
  • CRF-2: confirmed. codersdk/aibridge.go:90-92 says null happens "when any of those members has no limit"; the query returns null solely because group_totals inner-joins group_ai_budgets. UnbudgetedWithOverride (querier_test.go:12903-12909) has a member with an override limit of 1000 and still expects null, so the documented rule mispredicts a tested case. The SQL comment (sql:127-129) states the rule correctly. The wrong text is regenerated into typesGenerated.ts:6888 and docs/reference/api/schemas.md.
  • CRF-3: confirmed by reading, not executed. UpsertGroupAIBudgetRequest.SpendLimitMicros is int64 with only validate:"gte=0" (codersdk/aibridge.go:422) and the DB CHECK is >= 0 (dump.sql:2363), so a near-max limit is accepted and the new multiplication can raise bigint out of range for the statement, failing every queried group.
  • CRF-4: confirmed. db2sdk.OrganizationGroupAISpend feeds the single-group handler at enterprise/coderd/aibridge.go:1283, and no frontend code reads total_spend_limit_micros outside GroupsPageView.
  • No migrations or schema changes in this diff, so no CASCADE, trigger, or default side effects to enumerate.
About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

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.

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.

Comment thread coderd/database/queries/aicostcontrol.sql
-- 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)

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.

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 an int64 (codersdk/aibridge.go:422) plus the >= 0 CHECK constraint, so a group limit near int64 max is accepted. Two attributed members then overflow bigint, Postgres raises bigint out of range for the statement, and GetOrganizationGroupsAISpend returns 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.

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@johnstcn johnstcn Jul 28, 2026

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.

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.

Comment thread codersdk/aibridge.go Outdated
Comment thread codersdk/aibridge.go
@ssncferreira
ssncferreira force-pushed the ssncf/group-ai-total-spend-limit branch from 65beff7 to e768a87 Compare July 28, 2026 17:21
@ssncferreira
ssncferreira marked this pull request as ready for review July 28, 2026 18:02
@ssncferreira
ssncferreira requested review from a team, ibetitsmike and johnstcn as code owners July 28, 2026 18:02

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.

Potential follow-up: some EXPLAINs on dogfood (unless you've done that already)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ssncferreira
ssncferreira merged commit 0b40950 into main Jul 29, 2026
37 of 38 checks passed
@ssncferreira
ssncferreira deleted the ssncf/group-ai-total-spend-limit branch July 29, 2026 08:16
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants