Skip to content

Commit d8738b9

Browse files
authored
fix(billing): treat the per-user free valves as an org_limit denial
Local testing surfaced a coherence gap: hitting the free valve during a session showed the generic "try again shortly" modal, while the cloud pre-flight showed "Free usage used up / Add payment method" for the same state. The valves only exist for unsubscribed orgs, so classify their 429 wording as org_limit — both paths now land on the free-tier upsell. The free-tier copy drops the "billing period" over-claim (a daily valve resets sooner) and carries the reset hint when the caller has one. Generated-By: PostHog Code Task-Id: 1039ea23-9930-44e2-9888-b05cf8b129ec
1 parent 05bab7d commit d8738b9

5 files changed

Lines changed: 29 additions & 4 deletions

File tree

packages/core/src/billing/usageLimitContent.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ describe("usageLimitContent", () => {
3333
},
3434
);
3535

36+
it("includes the reset hint in the free-tier copy when available", () => {
37+
const content = usageLimitContent({
38+
cause: "org_limit",
39+
resetLabel: "Resets in 3h",
40+
subscribed: false,
41+
});
42+
expect(content.title).toBe("Free usage used up");
43+
expect(content.description).toContain("Resets in 3h");
44+
});
45+
3646
it("renders generic copy without a billing CTA when the cause is unknown", () => {
3747
const content = usageLimitContent({
3848
cause: null,

packages/core/src/billing/usageLimitContent.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ export function usageLimitContent(args: {
2828
if (subscribed === false) {
2929
return {
3030
title: "Free usage used up",
31-
description:
32-
"Your organization has used its included PostHog Code usage for this billing period. Add a payment method to keep going — you only pay for what you use.",
31+
description: `Your organization has used up its included PostHog Code usage.${
32+
resetLabel ? ` ${resetLabel}.` : ""
33+
} Add a payment method to keep going — you only pay for what you use.`,
3334
actionLabel: "Add payment method",
3435
dismissLabel: "Not now",
3536
};

packages/core/src/sessions/sessionServicePromptRecovery.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ describe("SessionService gateway billing denials", () => {
155155
"Your team has reached its PostHog Code usage limit for this billing period.",
156156
expectedShow: { cause: "org_limit" },
157157
},
158+
{
159+
case: "a free-tier valve 429",
160+
message: "Rate limit exceeded: User burst rate limit exceeded",
161+
expectedShow: { cause: "org_limit" },
162+
},
158163
{
159164
case: "an unclassified rate limit",
160165
message: "[429] Too many requests",

packages/shared/src/errors.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ describe("classifyGatewayLimitError", () => {
111111
"Your team has reached its usage limit for this billing period.",
112112
"org_limit",
113113
],
114+
[
115+
// Per-user free valves fire only for unsubscribed orgs; the modal's
116+
// subscribed bit picks the free-tier copy.
117+
"Rate limit exceeded: User burst rate limit exceeded",
118+
"org_limit",
119+
],
120+
["Rate limit exceeded: User sustained rate limit exceeded", "org_limit"],
114121
])("classifies %j as %s", (message, expected) => {
115122
expect(classifyGatewayLimitError(message)).toBe(expected);
116123
});
@@ -127,8 +134,6 @@ describe("classifyGatewayLimitError", () => {
127134
it.each([
128135
"Rate limit exceeded",
129136
"Rate limit exceeded: Product rate limit exceeded",
130-
// Per-user valves are a pre-cutover mechanism; generic copy handles them.
131-
"Rate limit exceeded: User burst rate limit exceeded",
132137
"Your team has used its monthly PostHog AI credits.",
133138
"network down",
134139
])("returns null for %j", (message) => {

packages/shared/src/errors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ const MODEL_GATE_PATTERNS = ["needs a paid posthog plan"] as const;
8181
const ORG_LIMIT_PATTERNS = [
8282
"reached its posthog code usage limit",
8383
"reached its usage limit for this billing period",
84+
// Per-user free valves — billed orgs have none, so these always mean the
85+
// free tier is used up.
86+
"user burst rate limit exceeded",
87+
"user sustained rate limit exceeded",
8488
] as const;
8589

8690
const FATAL_SESSION_ERROR_PATTERNS = [

0 commit comments

Comments
 (0)