fix(desktop): use organization billing period for usage - #76877
Conversation
Generated-By: PostHog Code Task-Id: 6de5f42c-0443-4002-938a-106aef1648b5
|
😎 Merged successfully - details. |
|
Hey @adboio! 👋 It looks like your git author email on this PR isn't your
You can fix it for this repo with: git config user.email "you@posthog.com"Or set it globally with |
🤖 CI report✅ Bundle size — no changeUncompressed size of every built Total: 65.58 MiB · no change No file changed by more than 1000 B. Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report ✅ Eager graph — within budgetHow much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy
🟢 Largest files eagerly shipped from
|
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 762 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 285.5 KiB | ../node_modules/.pnpm/posthog-js@1.410.1/node_modules/posthog-js/dist/rrweb.js |
| 267.7 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 235.5 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 231.5 KiB | ../node_modules/.pnpm/posthog-js@1.410.1/node_modules/posthog-js/dist/module.js |
| 154.3 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 105.3 KiB | src/lib/api.ts |
| 94.7 KiB | ../packages/quill/packages/quill/dist/index.js |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 90.6 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479
✅ Toolbar bundle — eager 2.19 MiB within budget
What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.
| Metric | Size | Δ vs base | Budget |
|---|---|---|---|
| Eager (shipped) entry + static imports |
2.19 MiB · 17 files | no change | ████░░░░░░ 38.3% of 5.72 MiB |
| Deferred (lazy) | 2.08 MiB · 33 files | no change | n/a — loads on demand |
Loader dist/toolbar.js |
1.1 KiB | no change | █░░░░░░░░░ 5.8% of 19.5 KiB |
Largest eagerly-shipped chunks
| Size | File |
|---|---|
| 718.1 KiB | dist/toolbar/toolbar-app-6ZY7LRCK.css |
| 551.5 KiB | dist/toolbar/chunk-chunk-OOWXB5XU.js |
| 484.6 KiB | dist/toolbar/chunk-chunk-HYIA72SV.js |
| 133.6 KiB | dist/toolbar/chunk-chunk-WRWH4RQO.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-T5KY5WYR.js |
| 71.0 KiB | dist/toolbar/toolbar-app-NVWWGTVG.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-27JL52RE.js |
| 35.6 KiB | dist/toolbar/chunk-chunk-FVSASBPN.js |
| 20.9 KiB | dist/toolbar/chunk-chunk-OWVP5BP2.js |
| 12.2 KiB | dist/toolbar/chunk-chunk-PIK3PADE.js |
Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile
✅ Dist folder size — 🔺 +6.4 KiB (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1389.36 MiB · 🔺 +6.4 KiB (+0.0%)
ℹ️ MCP UI apps size — 32 app(s), 17070.3 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 599.6 KB | 187.7 KB |
| action | 457.8 KB | 187.7 KB |
| action-list | 564.4 KB | 187.7 KB |
| cohort | 456.8 KB | 187.7 KB |
| cohort-list | 563.4 KB | 187.7 KB |
| email-template | 456.6 KB | 187.7 KB |
| error-details | 472.4 KB | 187.7 KB |
| error-issue | 457.5 KB | 187.7 KB |
| error-issue-list | 564.3 KB | 187.7 KB |
| experiment | 561.5 KB | 187.7 KB |
| experiment-list | 565.2 KB | 187.7 KB |
| experiment-results | 563.2 KB | 187.7 KB |
| feature-flag | 567.2 KB | 187.7 KB |
| feature-flag-list | 570.9 KB | 187.7 KB |
| feature-flag-testing | 461.0 KB | 187.7 KB |
| insight-actors | 562.2 KB | 187.7 KB |
| invite-email-preview | 456.0 KB | 187.7 KB |
| llm-costs | 559.5 KB | 187.7 KB |
| session-recording | 458.6 KB | 187.7 KB |
| session-summary | 463.9 KB | 187.7 KB |
| survey | 458.4 KB | 187.7 KB |
| survey-global-stats | 562.2 KB | 187.7 KB |
| survey-list | 565.1 KB | 187.7 KB |
| survey-stats | 562.2 KB | 187.7 KB |
| trace-span | 457.2 KB | 187.7 KB |
| trace-span-list | 564.3 KB | 187.7 KB |
| workflow | 457.1 KB | 187.7 KB |
| workflow-list | 563.7 KB | 187.7 KB |
| loops-review | 461.2 KB | 187.7 KB |
| query-results | 747.4 KB | 187.7 KB |
| render-ui | 828.0 KB | 187.7 KB |
| visual-review-snapshots | 461.6 KB | 187.7 KB |
✅ Backend coverage — all changed backend lines covered
🧪 Backend test coverage
Patch coverage — changed backend lines (products + core): ████████████████████ 100.0% (28 / 28)
All changed backend lines are covered ✅
Per-product line coverage (touched products)
| Product | Coverage | Lines |
|---|---|---|
platform_features |
██░░░░░░░░░░░░░░░░░░ 12.1% |
7 / 58 |
batch_exports |
████████░░░░░░░░░░░░ 39.5% |
8,853 / 22,413 |
warehouse_sources_queue |
█████████░░░░░░░░░░░ 45.9% |
90 / 196 |
demo |
███████████░░░░░░░░░ 56.3% |
1,497 / 2,661 |
data_tools |
█████████████░░░░░░░ 66.2% |
90 / 136 |
tasks |
██████████████░░░░░░ 70.4% |
35,223 / 50,047 |
ai_gateway |
███████████████░░░░░ 75.0% |
9 / 12 |
signals |
█████████████████░░░ 82.5% |
27,210 / 32,964 |
apm |
█████████████████░░░ 83.8% |
1,220 / 1,455 |
cdp |
█████████████████░░░ 84.2% |
3,914 / 4,649 |
notebooks |
█████████████████░░░ 86.4% |
8,029 / 9,296 |
data_modeling |
█████████████████░░░ 86.6% |
8,451 / 9,761 |
actions |
█████████████████░░░ 86.6% |
717 / 828 |
wizard |
█████████████████░░░ 86.7% |
1,090 / 1,257 |
cohorts |
██████████████████░░ 87.6% |
6,482 / 7,400 |
managed_warehouse |
██████████████████░░ 87.8% |
5,928 / 6,752 |
product_tours |
██████████████████░░ 87.9% |
1,303 / 1,482 |
data_warehouse |
██████████████████░░ 88.0% |
11,552 / 13,130 |
business_knowledge |
██████████████████░░ 89.0% |
4,384 / 4,928 |
engineering_analytics |
██████████████████░░ 89.2% |
6,725 / 7,537 |
exports |
██████████████████░░ 89.3% |
7,484 / 8,378 |
dashboards |
██████████████████░░ 89.3% |
5,995 / 6,710 |
visual_review |
██████████████████░░ 89.5% |
5,870 / 6,558 |
alerts |
██████████████████░░ 90.3% |
4,482 / 4,966 |
conversations |
██████████████████░░ 90.5% |
18,337 / 20,256 |
canvas |
██████████████████░░ 90.6% |
2,075 / 2,291 |
links |
██████████████████░░ 90.6% |
183 / 202 |
streamlit_apps |
██████████████████░░ 90.7% |
2,630 / 2,901 |
error_tracking |
██████████████████░░ 91.1% |
11,135 / 12,225 |
stamphog |
██████████████████░░ 91.3% |
4,505 / 4,936 |
slack_app |
██████████████████░░ 91.5% |
10,545 / 11,523 |
managed_migrations |
███████████████████░ 92.6% |
1,556 / 1,681 |
notifications |
███████████████████░ 92.6% |
1,017 / 1,098 |
mcp_analytics |
███████████████████░ 92.7% |
3,810 / 4,112 |
ai_observability |
███████████████████░ 92.9% |
17,116 / 18,426 |
early_access_features |
███████████████████░ 92.9% |
1,347 / 1,450 |
marketing_analytics |
███████████████████░ 92.9% |
14,833 / 15,967 |
mcp_store |
███████████████████░ 92.9% |
6,952 / 7,482 |
web_analytics |
███████████████████░ 93.1% |
15,963 / 17,154 |
surveys |
███████████████████░ 93.1% |
5,858 / 6,290 |
posthog_ai |
███████████████████░ 93.3% |
1,327 / 1,423 |
reminders |
███████████████████░ 93.4% |
468 / 501 |
product_analytics |
███████████████████░ 93.5% |
7,027 / 7,517 |
approvals |
███████████████████░ 93.5% |
3,491 / 3,734 |
legal_documents |
███████████████████░ 93.8% |
1,628 / 1,736 |
workflows |
███████████████████░ 94.3% |
7,951 / 8,436 |
endpoints |
███████████████████░ 94.3% |
8,771 / 9,306 |
tracing |
███████████████████░ 94.4% |
2,678 / 2,837 |
review_hog |
███████████████████░ 94.6% |
8,246 / 8,715 |
skills |
███████████████████░ 94.8% |
3,478 / 3,669 |
messaging |
███████████████████░ 95.4% |
3,401 / 3,565 |
replay_vision |
███████████████████░ 95.7% |
18,103 / 18,923 |
experiments |
███████████████████░ 95.7% |
27,865 / 29,109 |
logs |
███████████████████░ 95.8% |
11,623 / 12,135 |
annotations |
███████████████████░ 96.2% |
732 / 761 |
revenue_analytics |
███████████████████░ 96.3% |
1,887 / 1,960 |
growth |
███████████████████░ 96.3% |
4,845 / 5,031 |
feature_flags |
███████████████████░ 96.4% |
17,580 / 18,238 |
user_interviews |
███████████████████░ 96.5% |
2,638 / 2,734 |
access_control |
███████████████████░ 96.9% |
870 / 898 |
customer_analytics |
███████████████████░ 97.1% |
10,838 / 11,160 |
warehouse_sources |
███████████████████░ 97.5% |
380,177 / 390,026 |
data_catalog |
████████████████████ 97.9% |
2,676 / 2,734 |
analytics_platform |
████████████████████ 98.1% |
2,254 / 2,298 |
metrics |
████████████████████ 98.2% |
2,491 / 2,536 |
pulse |
████████████████████ 98.4% |
2,017 / 2,049 |
live_debugger |
████████████████████ 99.2% |
613 / 618 |
field_notes |
████████████████████ 99.4% |
158 / 159 |
Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.
Generated-By: PostHog Code Task-Id: 6de5f42c-0443-4002-938a-106aef1648b5
Generated-By: PostHog Code Task-Id: 6de5f42c-0443-4002-938a-106aef1648b5
🦔 ReviewHog reviewed this pull requestFound 2 must fix, 2 should fix, 0 consider. Published 4 findings (view the review). |
Prompt To Fix All With AI### Issue 1
services/llm-gateway/src/llm_gateway/services/billing_period_resolver.py:44-46
**Billing period uses mismatched scope**
When a user's current team belongs to a different organization than their current-organization preference, this request sends no team identifier, so Django returns the preference organization's period and the gateway caches it under the requested team, causing Desktop to display the wrong billing-period end for up to the cache TTL.
### Issue 2
services/llm-gateway/src/llm_gateway/services/billing_period_resolver.py:45-52
**Malformed responses bypass fallback**
If the billing endpoint or an intermediary returns a successful response with invalid JSON, `response.json()` raises outside the error handler and propagates through `asyncio.gather`, causing `/v1/usage` to return HTTP 500 instead of using the legacy seat-derived fallback.
```suggestion
try:
response = await self._http.get(url, headers={"Authorization": auth_header}, timeout=2.0)
response.raise_for_status()
data = response.json()
except (httpx.HTTPError, json.JSONDecodeError):
logger.warning("billing_period_fetch_failed", team_id=team_id, exc_info=True)
return None
```
### Issue 3
services/llm-gateway/src/llm_gateway/services/billing_period_resolver.py:18-21
**Resolver violates Python conventions**
The new same-typed frozen dataclass remains positional, `upstream_auth_header` is imported inside the resolver function, and the public billing endpoint has no matching documentation update. This permits silently swapped period boundaries, obscures the module dependency, and leaves the new API undocumented; make the dataclass keyword-only, move the import to module scope, and add the corresponding API documentation.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "fix(billing): type synchronized billing ..." | Re-trigger Greptile |
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
|
ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏 |
richardsolomou
left a comment
There was a problem hiding this comment.
could we test organization-period precedence when the seat has a different billing period? the current no-seat case still passes if precedence is accidentally reversed.
Generated-By: PostHog Code Task-Id: 6de5f42c-0443-4002-938a-106aef1648b5
Generated-By: PostHog Code Task-Id: 6de5f42c-0443-4002-938a-106aef1648b5
Generated-By: PostHog Code Task-Id: 6de5f42c-0443-4002-938a-106aef1648b5
Use the synchronized organization billing period's named fields after its tuple-to-dataclass migration. Generated-By: PostHog Code Task-Id: 19e58639-375a-4f6b-95a9-cccad9135d76
Generated-By: PostHog Code Task-Id: 19e58639-375a-4f6b-95a9-cccad9135d76
Problem
PostHog Desktop can label a per-user safety-throttle reset as the organization billing-period end when retired seats no longer provide billing periods. This makes Desktop disagree with the Billing UI.
Changes
Add a lightweight Billing-owned endpoint for the locally synchronized organization period, then have the gateway usage endpoint prefer it while retaining the legacy seat-derived period as a compatibility fallback. Keep quota limits focused on quota enforcement and remove the unrelated Tasks API ownership and generated clients.
Why: Desktop needs the same organization billing boundary as the Billing UI without putting billing metadata into the shared quota contract or calling the full external Billing payload.
How did you test this code?
uv run pytest tests/test_billing_period_resolver.py tests/test_quota_resolver.py tests/test_usage.py -qinservices/llm-gateway- 61 passedAutomatic notifications
Docs update
No docs change. This corrects the source of an existing billing-period label.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Codex revised the implementation after architectural review. The organization period now comes from a narrow Billing-owned API, and the quota endpoint remains limited to quota state.
Created with PostHog Code