feat(billing): add posthog code token spend analysis banner#2224
Open
pauldambra wants to merge 5 commits into
Open
feat(billing): add posthog code token spend analysis banner#2224pauldambra wants to merge 5 commits into
pauldambra wants to merge 5 commits into
Conversation
Surfaces a self-serve spend analysis inside Settings -> Plan & usage. Calls the new /api/llm_analytics/posthog_code_spend/ endpoint, renders totals + breakdowns by ai_product / tool / model + top traces, and generates inline heuristic suggestions on where to optimise. Footer links to PostHog LLM analytics docs and the exploring-llm-costs skill. Gated behind the posthog-code-spend-analysis feature flag (always on in dev). Plan & usage section itself is also forced on in dev so the banner is reachable without billing-flag setup. Generated-By: PostHog Code Task-Id: f9d5d152-49c6-46cf-8fde-079105ba2e67
Contributor
Prompt To Fix All With AIFix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
apps/code/src/renderer/features/billing/hooks/useTokenSpendAnalysis.ts:28-33
Refresh errors are silently swallowed after the first successful load. Because `data` is never cleared on failure, the banner always takes the `if (data)` branch in `TokenSpendAnalysisBanner`, so the `if (error)` branch is unreachable once data has been fetched. When a refresh fails the user sees the stale previous result with no indication anything went wrong.
```suggestion
const result = await client.getPostHogCodeSpendAnalysis(days);
setData(result);
setError(null);
} catch (err) {
const message = err instanceof Error ? err.message : "Unknown error";
log.warn("Failed to fetch spend analysis", { error: message });
setData(null);
setError(message);
```
### Issue 2 of 3
apps/code/src/renderer/features/billing/components/TokenSpendAnalysisBanner.tsx:23-24
The `amount < 1` branch is a strict subset of `amount < 100` and performs the exact same operation (`toFixed(2)`), so it is dead code that adds no distinction. This violates the "no superfluous parts" simplicity rule.
```suggestion
if (amount < 100) return `$${amount.toFixed(2)}`;
```
### Issue 3 of 3
apps/code/src/renderer/features/billing/types/spend-analysis.ts:22-28
`SpendAnalysisModelRow` is defined and `by_model` is included in `SpendAnalysisResponse`, but the component never renders a model breakdown table. The data is fetched over the wire and typed, yet discarded. Either a `ModelTable` is missing from the banner, or the interface and field should be dropped to satisfy the "no superfluous parts" rule.
Reviews (1): Last reviewed commit: "feat(billing): add posthog code token sp..." | Re-trigger Greptile |
- Clear data state on fetch error so error UI is reachable on refresh failures. - Render the model breakdown table that was already typed and fetched but unused. - Remove redundant amount < 1 branch in formatUsd. Generated-By: PostHog Code Task-Id: f9d5d152-49c6-46cf-8fde-079105ba2e67
joshsny
approved these changes
May 19, 2026
Contributor
joshsny
left a comment
There was a problem hiding this comment.
LGTM - wondering if it should be an MCP tool and we send them into the chat, probably not since they want to look at this when they ran out of $ so that would be frustrating
Renames the client method to getPersonalSpendAnalysis, takes an options object with optional `product` query param, and points at the new /api/llm_analytics/personal_spend/ endpoint. Banner passes `product: "posthog_code"` so the tool / model / trace breakdowns stay PostHog Code-scoped; the by_product breakdown shows the cross-product distribution. SpendAnalysisSummary drops posthog_code_cost_usd / posthog_code_event_count in favour of generic scoped_cost_usd / scoped_event_count fields, matching the backend shape. Hook renamed useTokenSpendAnalysis -> useSpendAnalysis. Generated-By: PostHog Code Task-Id: f9d5d152-49c6-46cf-8fde-079105ba2e67
…aginated
Mirrors the backend changes:
- URL: getPersonalSpendAnalysis now calls /api/llm_analytics/@me/spend/
- Response shape: each breakdown is `{ items, truncated }`; banner reads
`.items` and tables stay the same.
- SpendAnalysisToolRow gains `share_of_scoped` (float 0-1). Banner
suggestions use this directly, no longer divide cost_usd by
scoped_cost_usd (which can over-count for multi-tool generations).
Generated-By: PostHog Code
Task-Id: f9d5d152-49c6-46cf-8fde-079105ba2e67
Tracks the backend rename:
- Client method now takes `{ dateFrom, dateTo, product }` instead of
`{ days, product }`. Maps to the `date_from` / `date_to` query params
the backend now accepts.
- SpendAnalysisSummary swaps `period_days` for `date_from` / `date_to`
ISO strings.
- Banner derives the "Window" stat card from the two timestamps.
Generated-By: PostHog Code
Task-Id: f9d5d152-49c6-46cf-8fde-079105ba2e67
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PostHog Code users on the Plan & usage page have no way to see how their LLM token spend breaks down. The data exists in PostHog's LLM analytics — we just haven't surfaced it back inside the app. This PR adds an in-app banner that runs the analysis on demand and prints the result inline, doubling as an upsell for PostHog's LLM analytics product.
Changes
TokenSpendAnalysisBannerrendered at the top ofSettings -> Plan & usage. Idle state is an upsell callout with an "Analyse my spend" button; click runs the analysis, swaps to inline tables and heuristic suggestions on completion.useTokenSpendAnalysishook (renderer-sideuseState/useEffectpattern matching other cloud-API callers) and a newgetPostHogCodeSpendAnalysis(days)method onPostHogAPIClientthat hitsGET /api/llm_analytics/posthog_code_spend/.apps/code/src/renderer/features/billing/types/spend-analysis.ts, mirroring the backend serializer.exploring-llm-costsskill in the skill store.posthog-code-spend-analysis(always on in dev). TheBILLING_FLAGthat gates Plan & usage itself is also forced on in dev so the section is reachable without per-developer flag setup.Backend endpoint: PostHog/posthog#59017.
Screenshots
Banner idle state — Settings → Plan & usage:
How did you test this code?
I'm an agent. I did not perform manual UI testing — the screenshot above is from the human author's local run.
pnpm --filter=@posthog/code typecheckpasses on the rebuilt branch.pnpm biome check --write --unsafeclean on the touched files.The banner is reachable in dev at Cmd+, → Plan & usage.
Publish to changelog?
no — backend dependency lands first; user-facing announcement comes later.
🤖 Agent context
Authored with PostHog Code (Claude Opus 4.7).