From ffd5346effac25d99e82230776cc05856bbc90eb Mon Sep 17 00:00:00 2001
From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com>
Date: Thu, 23 Jul 2026 22:00:22 -0700
Subject: [PATCH 1/2] test initial Pro checkout destination
---
web/tests/pro-cta-link.test.tsx | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
create mode 100644 web/tests/pro-cta-link.test.tsx
diff --git a/web/tests/pro-cta-link.test.tsx b/web/tests/pro-cta-link.test.tsx
new file mode 100644
index 000000000000..8a69ed83185b
--- /dev/null
+++ b/web/tests/pro-cta-link.test.tsx
@@ -0,0 +1,30 @@
+import { describe, expect, mock, test } from "bun:test";
+import { renderToStaticMarkup } from "react-dom/server";
+
+const capture = mock(() => undefined);
+
+mock.module("posthog-js", () => ({
+ default: { capture },
+}));
+
+const { ProCtaLink } = await import(
+ "../app/[locale]/components/pro-cta-link"
+);
+
+describe("Pro pricing CTA", () => {
+ test("routes the initial click to Stripe checkout", () => {
+ const html = renderToStaticMarkup(
+
+ Get Pro
+ ,
+ );
+
+ expect(html).toContain(
+ 'href="/api/billing/checkout?plan=pro"',
+ );
+ expect(html).not.toContain('href="/download/confirmation?dl=1"');
+ });
+});
From ef420168e1c53d06352ba94b54d100cfaec57991 Mon Sep 17 00:00:00 2001
From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com>
Date: Thu, 23 Jul 2026 22:03:19 -0700
Subject: [PATCH 2/2] route Pro pricing CTA directly to checkout
---
docs/pro-badge-options.html | 3 +-
scripts/retired-feature-flags.txt | 1 +
skills/cmux-billing/SKILL.md | 2 +-
web/app/[locale]/components/pro-cta-link.tsx | 35 ++++----------------
web/app/[locale]/pricing/page.tsx | 6 +---
web/app/lib/feature-flags.ts | 8 -----
web/tests/pro-cta-link.test.tsx | 1 -
7 files changed, 10 insertions(+), 46 deletions(-)
diff --git a/docs/pro-badge-options.html b/docs/pro-badge-options.html
index cb187d3284c9..d2925330afb3 100644
--- a/docs/pro-badge-options.html
+++ b/docs/pro-badge-options.html
@@ -189,9 +189,8 @@
Feature flags (PostHog project 244066)
pro-upgrade-ui-enabled-release — badge + all Pro entrypoints · ON
mobile-connect-button-enabled-release — titlebar iPhone button · ON
- pro-checkout-enabled-release — pricing CTA → checkout · OFF
- Each is registered in code (owner + review date + safe default), gated live from the PostHog dashboard, and enforced by scripts/lint-feature-flags.py in CI against the posthog.com feature-flag-mistakes rules.
+ Each is registered in code (owner + review date + safe default), gated live from the PostHog dashboard, and enforced by scripts/lint-feature-flags.py in CI against the posthog.com feature-flag-mistakes rules. Pricing checkout is launched and no longer feature-gated.
In-app pricing screen (native)
diff --git a/scripts/retired-feature-flags.txt b/scripts/retired-feature-flags.txt
index 9498d88d16cf..6523936d26ba 100644
--- a/scripts/retired-feature-flags.txt
+++ b/scripts/retired-feature-flags.txt
@@ -2,3 +2,4 @@
# from code. Keys listed here must NEVER be reused for new functionality —
# reusing a stale key is how Knight Capital lost $440M in 45 minutes.
# scripts/lint-feature-flags.py fails CI if any of these reappear.
+pro-checkout-enabled-release
diff --git a/skills/cmux-billing/SKILL.md b/skills/cmux-billing/SKILL.md
index c9833d7f0f22..a45af793fd2e 100644
--- a/skills/cmux-billing/SKILL.md
+++ b/skills/cmux-billing/SKILL.md
@@ -40,7 +40,7 @@ Use this skill before changing billing, pricing, Stripe, Pro entitlement, checko
## Feature Flags
- `pro-upgrade-ui-enabled-release` (PostHog id `741838`) gates all Pro UI and stays OFF in release until launch. DEBUG builds default the UI on.
-- `pro-checkout-enabled-release` (PostHog id `741839`) routes the public pricing CTA.
+- Public Pro and Team pricing CTAs always route through `/api/billing/checkout`; do not fall back to the download confirmation page.
- `cmux __internal_flags`, once merged, inspects and overrides flags locally.
## Prod Runbook
diff --git a/web/app/[locale]/components/pro-cta-link.tsx b/web/app/[locale]/components/pro-cta-link.tsx
index fb8ec87968ee..9834aa21b9db 100644
--- a/web/app/[locale]/components/pro-cta-link.tsx
+++ b/web/app/[locale]/components/pro-cta-link.tsx
@@ -9,51 +9,28 @@ import {
CheckoutPendingContent,
useCheckoutRedirect,
} from "../../components/checkout-navigation";
-import {
- isClientConfigFlagEnabled,
- useClientConfigFlag,
-} from "../../lib/client-config-flags";
-import { FEATURE_FLAGS } from "../../lib/feature-flags";
-
-// Single evaluation site for the pro-checkout flag (lint-enforced).
-// Resolution: build-time env force (local dev / previews), then the PostHog
-// flag, then the registry's safe default (the download link always works,
-// including while flags are still loading — no checkout flicker).
-const FORCE = process.env.NEXT_PUBLIC_CMUX_CHECKOUT_ENABLED;
-const FORCED_ON = FORCE === "1" || (FORCE === undefined && process.env.NODE_ENV === "development");
-const FORCED_OFF = FORCE === "0";
export function ProCtaLink({
checkoutHref,
- fallbackHref,
children,
size = "default",
location = "pricing_page",
}: {
checkoutHref: string;
- fallbackHref: string;
children: React.ReactNode;
size?: PricingActionSize;
location?: string;
}) {
- const flagEnabled = useClientConfigFlag(FEATURE_FLAGS.proCheckout.key);
- const checkout =
- !FORCED_OFF &&
- (FORCED_ON ||
- isClientConfigFlagEnabled(
- flagEnabled,
- FEATURE_FLAGS.proCheckout.defaultWhenUnavailable,
- ));
- const href = checkout ? checkoutHref : fallbackHref;
const { pending, start } = useCheckoutRedirect();
return (
{
- posthog.capture("cmuxterm_pro_cta_clicked", { location, checkout });
- // Only the checkout href is intercepted for the spinner; the download
- // fallback navigates normally.
- start(href, event);
+ posthog.capture("cmuxterm_pro_cta_clicked", {
+ location,
+ checkout: true,
+ });
+ start(checkoutHref, event);
}}
aria-busy={pending}
className={`${pricingActionClassName("primary", size)} relative`}
diff --git a/web/app/[locale]/pricing/page.tsx b/web/app/[locale]/pricing/page.tsx
index e1efda6c64e4..f6c8d9bd50c9 100644
--- a/web/app/[locale]/pricing/page.tsx
+++ b/web/app/[locale]/pricing/page.tsx
@@ -36,9 +36,6 @@ import {
} from "../../components/pricing-shared";
import { CheckoutButton } from "../../components/checkout-navigation";
-// The Pro CTA destination is decided at runtime by the proCheckout PostHog
-// flag inside (see app/lib/feature-flags.ts); the download
-// link is the safe fallback.
const ENTERPRISE_CTA_URL = "/enterprise";
const ANONYMOUS_IF_EXISTS = "anonymous-if-exists[deprecated]" as const;
@@ -152,7 +149,7 @@ export default async function PricingPage({
) : (
-
+
{t("pro.cta")}
)}
@@ -216,7 +213,6 @@ export default async function PricingPage({
) : (
diff --git a/web/app/lib/feature-flags.ts b/web/app/lib/feature-flags.ts
index 46009f8d979f..36a3fce4c419 100644
--- a/web/app/lib/feature-flags.ts
+++ b/web/app/lib/feature-flags.ts
@@ -32,12 +32,4 @@ export const FEATURE_FLAGS = {
reviewBy: "2026-10-01",
defaultWhenUnavailable: false,
},
- proCheckout: {
- key: "pro-checkout-enabled-release",
- owner: "lawrencecchen",
- description:
- "Points the pricing page Pro CTA at /api/billing/checkout instead of the download link. Off until prod Stripe is live.",
- reviewBy: "2026-10-01",
- defaultWhenUnavailable: false,
- },
} as const satisfies Record;
diff --git a/web/tests/pro-cta-link.test.tsx b/web/tests/pro-cta-link.test.tsx
index 8a69ed83185b..d29e687eae35 100644
--- a/web/tests/pro-cta-link.test.tsx
+++ b/web/tests/pro-cta-link.test.tsx
@@ -16,7 +16,6 @@ describe("Pro pricing CTA", () => {
const html = renderToStaticMarkup(
Get Pro
,