Skip to content

feat(billing): threshold notification service with persisted dedupe#2351

Draft
k11kirky wants to merge 1 commit into
posthog-code/usage-sidebar-reset-timefrom
posthog-code/usage-threshold-monitor
Draft

feat(billing): threshold notification service with persisted dedupe#2351
k11kirky wants to merge 1 commit into
posthog-code/usage-sidebar-reset-timefrom
posthog-code/usage-threshold-monitor

Conversation

@k11kirky
Copy link
Copy Markdown
Contributor

@k11kirky k11kirky commented May 25, 2026

Problem

Users had no visibility into their LLM usage approaching or reaching limits. Without proactive notifications, users would be surprised when hitting rate limits mid-session.

Changes

Introduced a UsageMonitorService that polls the LLM gateway every 30 seconds and emits threshold-crossed events when usage crosses 50%, 75%, 90%, or 100% for either the burst (daily) or sustained (monthly) bucket.

Key behaviors:

  • Only the highest threshold crossed in a given window is emitted per bucket — e.g. jumping straight to 95% fires a 90% event, not 50% and 75% as well
  • Deduplication is persisted to disk via electron-store so notifications don't re-fire after an app relaunch within the same billing window
  • Stale dedupe entries (past their anchor timestamp) are pruned on startup
  • Pro users are detected by the presence of billing_period_end on the usage response
  • Gateway errors are swallowed silently so polling never crashes the app

On the renderer side, initializeUsageThresholdToast subscribes to the new usageMonitor.onThresholdCrossed tRPC subscription and shows:

  • A warning toast with a "View usage" action for 50/75/90% thresholds
  • An error toast or the existing UsageLimitModal (when a session is active) at 100%

The old useUsageLimitDetection hook and its polling-based approach have been removed in favour of this event-driven model. The toast.warning utility was extended to support an action button.

How did you test this?

Unit tests cover the core deduplication and emission logic in UsageMonitorService:

  • Emits at the correct threshold and suppresses duplicate events within the same anchor window
  • Only fires the highest crossed threshold, not every threshold below it
  • Persisted dedupe state survives a simulated relaunch
  • Burst and sustained buckets are tracked independently
  • isPro is correctly derived from billing_period_end
  • Gateway errors resolve to null without throwing

Publish to changelog?

no

Copy link
Copy Markdown
Contributor Author

k11kirky commented May 25, 2026

Moves usage-limit detection out of the renderer into a main-process
`UsageMonitorService` that polls /v1/usage every 30s, detects when a
bucket newly crosses 50/75/90/100%, and emits an event through a
tRPC subscription. Dedupe state lives in a persistent electron-store
keyed by `${userId}:${product}:${bucket}:${anchor}:${threshold}` so
crossings don't re-fire after an app relaunch. Anchors are
`reset_at` rounded to the hour for burst (jitter-tolerant), and
`billing_period_end` (Pro) or the date of `reset_at` (Free) for
sustained. Stale entries are pruned on startup.

The renderer subscribes via `initializeUsageThresholdToast` (modelled
on `connectivityToast`) and shows a warning toast at 50/75/90% with
a "View usage" action that opens the Plan & Usage settings. At 100%
the existing `UsageLimitModal` opens if a session is active, else
the user gets a blocking error toast.

`useUsageLimitDetection` is deleted — the 100% path is now driven
from the same subscription. The renderer holds no detection state.
`toast.warning` is extended to forward an action button (the wiring
already exists in `ToastComponent`).

Generated-By: PostHog Code
Task-Id: bac06178-1ab1-4000-9a56-1901215bd4af

Generated-By: PostHog Code
Task-Id: bac06178-1ab1-4000-9a56-1901215bd4af
@k11kirky k11kirky force-pushed the posthog-code/usage-sidebar-reset-time branch from a6fca1b to 211250e Compare May 25, 2026 16:58
@k11kirky k11kirky force-pushed the posthog-code/usage-threshold-monitor branch from d6105bb to 1589fab Compare May 25, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant