Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
f2f2257
test(pricing): require shared Cloud VM capacity copy
lawrencecchen Sep 4, 2026
3f51676
fix(pricing): describe Cloud VM capacity as shared
lawrencecchen Sep 4, 2026
dde5b8d
test(pricing): cover shared Team capacity copy
lawrencecchen Sep 4, 2026
afb58e7
docs(vms): distinguish shared pricing from VM sizing
lawrencecchen Sep 4, 2026
ceec386
fix(pricing): make Team VM allowance explicit
lawrencecchen Sep 4, 2026
8583918
Merge origin/main into feat-shared-cloud-vm-capacity
lawrencecchen Sep 4, 2026
80ccdc7
Merge remote-tracking branch 'origin/main' into feat-shared-cloud-vm-…
lawrencecchen Sep 4, 2026
0142157
Enforce shared Cloud VM capacity
lawrencecchen Sep 4, 2026
ca819b5
Keep free fork provisioning outside paid quota
lawrencecchen Sep 4, 2026
6ce2818
Model shared VM resource claims correctly
lawrencecchen Sep 4, 2026
5a35100
Merge remote-tracking branch 'origin/main' into feat-shared-cloud-vm-…
lawrencecchen Sep 4, 2026
3944088
Account for legacy VM disks when forking
lawrencecchen Sep 4, 2026
5111e8d
Enforce additive shared Cloud VM capacity
lawrencecchen Sep 4, 2026
5806281
Merge remote-tracking branch 'origin/main' into feat-shared-cloud-vm-…
lawrencecchen Sep 4, 2026
a0eb6f2
Model shared CPU and memory ceilings
lawrencecchen Sep 4, 2026
8ccfce3
Merge remote-tracking branch 'origin/main' into feat-shared-cloud-vm-…
lawrencecchen Sep 4, 2026
ffa2936
Reserve legacy and restored VM disk claims
lawrencecchen Sep 4, 2026
5d3f33d
Merge remote-tracking branch 'origin/main' into feat-shared-cloud-vm-…
lawrencecchen Sep 4, 2026
625bda6
Document fallback locale pricing policy
lawrencecchen Sep 4, 2026
437ad48
test: cover legacy VM resource reconciliation
lawrencecchen Sep 4, 2026
3617706
fix: reconcile legacy VM resource claims
lawrencecchen Sep 4, 2026
30e7e0b
Merge remote-tracking branch 'origin/main' into recover-shared-cloud-…
lawrencecchen Sep 4, 2026
29f88ff
Merge remote-tracking branch 'origin/main' into recover-shared-cloud-…
lawrencecchen Sep 4, 2026
63c415c
test: cover resize and Base recovery resource claims
lawrencecchen Sep 4, 2026
3cf901b
fix: persist confirmed VM resize claims
lawrencecchen Sep 4, 2026
b4c652f
fix: type resize claim JSON update
lawrencecchen Sep 4, 2026
000dce1
fix: clarify paid VM pool scope
lawrencecchen Sep 4, 2026
9328816
test: cover unmeasured and pending VM claims
lawrencecchen Sep 4, 2026
6082e47
fix: keep resize headroom claims race safe
lawrencecchen Sep 4, 2026
f549aec
test: cover shared resource and resize recovery races
lawrencecchen Sep 4, 2026
77999e2
fix: enforce shared VM resources and resize recovery
lawrencecchen Sep 4, 2026
6c94f00
Merge remote-tracking branch 'origin/main' into recover-shared-cloud-…
lawrencecchen Sep 4, 2026
f2dbec4
chore: account for intentional VM orchestration complexity
lawrencecchen Sep 4, 2026
f38ebe2
fix: move legacy resource repair off VM request paths
lawrencecchen Sep 4, 2026
0089b63
fix: preserve shared resource operation phase
lawrencecchen Sep 4, 2026
73f5315
fix: isolate resize control metadata
lawrencecchen Sep 4, 2026
7871e5b
test: verify provider metadata cannot replace resize generation
lawrencecchen Sep 4, 2026
f7775f8
test: cover image-sized VM reservation
lawrencecchen Sep 4, 2026
d4c57be
fix: reserve explicit image-sized VM resources
lawrencecchen Sep 4, 2026
0af1315
test: cover legacy VM shape recovery
lawrencecchen Sep 4, 2026
a5ec30b
fix: preserve legacy VM shape in forks and snapshots
lawrencecchen Sep 4, 2026
ce517ad
fix: use reported dimensions for legacy VM claims
lawrencecchen Sep 4, 2026
b95fd56
test: preserve recorded legacy snapshot shape
lawrencecchen Sep 4, 2026
78e8bc5
fix: honor recorded legacy snapshot resources
lawrencecchen Sep 4, 2026
9e77b3e
refactor: isolate snapshot reservation calculation
lawrencecchen Sep 4, 2026
5708b12
test: cover resize claim recovery after stats loss
lawrencecchen Sep 4, 2026
6f2d3ab
fix: make resize claims recoverable after stats loss
lawrencecchen Sep 4, 2026
1b7bdd9
Merge remote-tracking branch 'origin/main' into recover-shared-cloud-…
lawrencecchen Sep 4, 2026
2260ef8
test: cover reconcile retry and resize races
lawrencecchen Sep 4, 2026
2ecb6ac
fix: bound and recover resource reconciliation
lawrencecchen Sep 4, 2026
a2e803c
test: cover baked image disk reservations
lawrencecchen Sep 4, 2026
28cf29c
fix: reserve baked image disk for memory requests
lawrencecchen Sep 4, 2026
10d6154
test: cover bounded resize recovery
lawrencecchen Sep 4, 2026
bc82183
fix: bound unconfirmed resize recovery
lawrencecchen Sep 4, 2026
80e7835
test: cover no-op resize recovery
lawrencecchen Sep 4, 2026
90a0cd6
fix: preserve unconfirmed resize markers on stale retries
lawrencecchen Sep 4, 2026
008bf89
test: reject implausible legacy fork dimensions
lawrencecchen Sep 4, 2026
b54fb50
fix: bound provider resource dimensions
lawrencecchen Sep 4, 2026
3c50a04
test: preserve one-vCPU legacy fork shapes
lawrencecchen Sep 4, 2026
b4605b5
fix: accept one-vCPU provider shapes
lawrencecchen Sep 4, 2026
2c5f4ff
test: cover shared pool recovery edge cases
lawrencecchen Sep 4, 2026
cc14e08
fix: finalize shared VM resource claims safely
lawrencecchen Sep 4, 2026
515e772
test: cover image disk override reservation
lawrencecchen Sep 4, 2026
8bc965d
fix: honor disk override for image reservations
lawrencecchen Sep 4, 2026
056c6f5
refactor: split legacy resource reconciliation
lawrencecchen Sep 4, 2026
13667ae
test: cover restore and fork resource recovery
lawrencecchen Sep 4, 2026
54c58b3
fix: close restore and fork quota gaps
lawrencecchen Sep 4, 2026
3b5ae19
test: cover snapshot and status refresh races
lawrencecchen Sep 4, 2026
fffb09d
fix: refresh snapshot and provider resource state
lawrencecchen Sep 4, 2026
9533d36
Merge remote-tracking branch 'origin/main' into recover-shared-cloud-…
lawrencecchen Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4374,7 +4374,8 @@ struct CMUXCLI {
// `vm_image_config_error`.
/// `--size` spellings → memory in MB. The supported base-image ladder is
/// 4 GB, 8 GB, 16 GB, 24 GB, 32 GB, and 64 GB of RAM, with disk sizes
/// following each image.
/// following each image. Pricing separately describes 5 vCPU, 20 GB RAM,
/// and 200 GB disk as one pool shared across the plan's Cloud VMs.
private static let cloudVMSizeAliases: [String: Int] = [
"4g": 4096, "4gb": 4096,
"8g": 8192, "8gb": 8192,
Expand Down
56 changes: 46 additions & 10 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -150566,13 +150566,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Up to 50 Cloud VMs, each with 8 GB RAM and 32 GB disk by default; sizes from 4 to 64 GB RAM are available"
"value": "Up to 50 Cloud VMs, all sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk; each VM starts at 8 GB RAM and 32 GB disk, with sizes from 4 to 64 GB RAM available as capacity allows"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "最大 50 台の Cloud VM、各 8 GB RAM / 32 GB ディスク(標準)。4~64 GB RAM のサイズを利用できます"
"value": "最大 50 台の Cloud VM、すべての VM で合計 5 vCPU / 20 GB RAM / 200 GB ディスクを共有。標準サイズは 8 GB RAM / 32 GB ディスクで、容量の範囲内で RAM 4~64 GB のサイズを選べます"
}
}
}
Expand Down Expand Up @@ -150787,15 +150787,33 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Every Cloud VM has a configurable size: 4, 8, 16, 24, 32, or 64 GB RAM with 16, 32, 64, 96, or 128 GB disk. Pro and Team include up to 50 machines with no metering or overages."
"value": "Pro includes up to 50 Cloud VMs sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same shared pool per user. The default VM size is 8 GB RAM and 32 GB disk; sizes from 4 to 64 GB RAM are available as capacity allows. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "すべての Cloud VM はサイズを選択できます。RAM は 4、8、16、24、32、64 GB、ディスクは 16、32、64、96、128 GB です。Pro と Team には最大 50 台のマシンが含まれ、従量課金や超過料金はありません。"
"value": "Pro では最大 50 台の Cloud VM で、合計 5 vCPU / 20 GB RAM / 200 GB ディスクを共有します。Team では同じ共有プールがユーザーごとに含まれます。標準 VM サイズは 8 GB RAM / 32 GB ディスクで、容量の範囲内で RAM 4~64 GB のサイズを選べます。新しい VM のディスクは 32 GB で開始し、共有プールの範囲内で拡張できます。従量課金や超過料金はありません。"
}
}
},
"ar": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"bs": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"da": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"de": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"es": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"fr": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"it": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"km": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"ko": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"nb": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"pl": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"pt-BR": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"ru": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"th": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"tr": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"uk": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } },
"zh-Hant": { "stringUnit": { "state": "translated", "value": "Pro includes up to 50 Cloud VMs, all sharing a total of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same allowance and shared capacity per user. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing." } }
}
},
"pricing.native.sizes.colRate": {
Expand Down Expand Up @@ -150855,15 +150873,33 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Cloud VM sizes"
"value": "Shared Cloud VM resources"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "Cloud VM サイズ"
"value": "Cloud VM 間で共有するリソース"
}
}
},
"ar": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"bs": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"da": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"de": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"es": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"fr": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"it": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"km": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"ko": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"nb": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"pl": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"pt-BR": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"ru": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"th": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"tr": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"uk": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } },
"zh-Hant": { "stringUnit": { "state": "translated", "value": "Shared Cloud VM resources" } }
}
},
"pricing.native.status.billingUnavailable": {
Expand Down Expand Up @@ -150974,13 +151010,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Up to 50 Cloud VMs per user"
"value": "Up to 50 Cloud VMs per user, all sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk; each VM starts at 8 GB RAM and 32 GB disk, with sizes from 4 to 64 GB RAM available as capacity allows"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ユーザーごとに最大 50 台の Cloud VM"
"value": "ユーザーごとに最大 50 台の Cloud VM、すべての VM で合計 5 vCPU / 20 GB RAM / 200 GB ディスクを共有。標準サイズは 8 GB RAM / 32 GB ディスクで、容量の範囲内で RAM 4~64 GB のサイズを選べます"
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion Sources/Cloud/NewMachineModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,13 @@ final class NewMachineModel {
static let legacyPlanMachineMemoryMb = 20480
/// Mirrors `maxMemoryMbForPlan`: development and paid plans may use the
/// largest supported base image unless an operator sets a lower ceiling.
/// The pricing page separately describes the 5 vCPU / 20 GB RAM / 200 GB
/// disk pool shared across a paid plan's Cloud VMs.
static func maxMemoryMb(planId: String?) -> Int {
_ = planId
return memoryOptionsMb.max() ?? planMachineMemoryMb
}
/// Mirrors `defaultMemoryMbForPlan`: the plan machine, never above the max.
/// Mirrors `defaultMemoryMbForPlan`: the provider sizing profile, never above the max.
static func defaultMemoryMb(planId: String?) -> Int {
min(planMachineMemoryMb, maxMemoryMb(planId: planId))
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/PricingPlansScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ private struct NativePricingPlansView: View {
isProminent: true,
features: [
String(localized: "pricing.native.pro.feature.vms", defaultValue: "Cloud agents on isolated Cloud VMs"),
String(localized: "pricing.native.pro.feature.hours", defaultValue: "Up to 50 Cloud VMs, each with 5 vCPU, 20 GB RAM, and 32 GB disk"),
String(localized: "pricing.native.pro.feature.hours", defaultValue: "Up to 50 Cloud VMs, all sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk; each VM starts at 8 GB RAM and 32 GB disk, with sizes from 4 to 64 GB RAM available as capacity allows"),
String(localized: "pricing.native.pro.feature.gateway", defaultValue: "Unlimited workspaces"),
String(localized: "pricing.native.pro.feature.ios", defaultValue: "cmux iOS app and email support"),
]
Expand All @@ -432,7 +432,7 @@ private struct NativePricingPlansView: View {
features: [
String(localized: "pricing.native.team.feature.billing", defaultValue: "Unified billing for the whole team"),
String(localized: "pricing.native.team.feature.seats", defaultValue: "Centralized seat management"),
String(localized: "pricing.native.team.feature.compute", defaultValue: "Up to 50 Cloud VMs per user"),
String(localized: "pricing.native.team.feature.compute", defaultValue: "Up to 50 Cloud VMs per user, all sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk; each VM starts at 8 GB RAM and 32 GB disk, with sizes from 4 to 64 GB RAM available as capacity allows"),
String(localized: "pricing.native.team.feature.gateway", defaultValue: "Team-wide model gateway analytics"),
String(localized: "pricing.native.team.feature.support", defaultValue: "Priority email support"),
]
Expand Down Expand Up @@ -762,12 +762,12 @@ private struct NativePricingTableCell: View {
private struct NativePricingSizeSection: View {
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(String(localized: "pricing.native.sizes.title", defaultValue: "Cloud VM sizes"))
Text(String(localized: "pricing.native.sizes.title", defaultValue: "Shared Cloud VM resources"))
.font(.system(size: 13, weight: .medium))
.foregroundStyle(.secondary)
Text(String(
localized: "pricing.native.sizes.body",
defaultValue: "Every Cloud VM is 5 vCPU, 20 GB RAM, and 32 GB disk. Disk can grow to 256 GiB. Pro and Team include up to 50 machines with no metering or overages."
defaultValue: "Pro includes up to 50 Cloud VMs sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same shared pool per user. The default VM size is 8 GB RAM and 32 GB disk; sizes from 4 to 64 GB RAM are available as capacity allows. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing."
))
.font(.system(size: 13))
.foregroundStyle(.secondary)
Expand Down
2 changes: 1 addition & 1 deletion skills/cmux-billing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Read before changing billing, pricing, Stripe, Pro entitlement, checkout, webhoo

Prices live in `web/services/billing/plans.ts` and are provisioned by `web/scripts/stripe/provision-catalog.sh`. Current checkout keys: `cmux-pro-monthly-50` ($50/mo), `cmux-pro-yearly-480` ($480/yr, $40/mo equivalent), `cmux-team-monthly-60` ($60/user/mo), `cmux-team-yearly-576` ($576/user/yr, $48/mo equivalent). Stripe amounts are immutable, so a price change mints a new lookup key carrying the amount and adds the old key to `LEGACY_PRICE_LOOKUP_KEYS`; grandfathered Prices (`cmux-pro-monthly` $30, `cmux-pro-yearly` $240, `cmux-pro-yearly-288` $288, `cmux-team-monthly` $35, `cmux-team-yearly-336` $336) stay active for the subscriptions on them and `/dashboard/billing` prices every row from its own Stripe amount. Env price-id overrides carry the amount in their name (`STRIPE_PRO_MONTHLY_50_PRICE_ID` and friends); a retired name fails env validation, so delete it from Vercel before deploying a price change. Test-mode Pro product `prod_UyHgRPpmCzrkLJ`, live `prod_Uq4a28vk0fP3E6`. Staging webhook endpoint `we_1Tq1SZGhInAdn3JbWJReKNEN` forwards to `cmux-staging.vercel.app`; its secrets are already in the `cmux-staging` Vercel project.

The plan machine (50 machines per billing team, 20 GB / 5 vCPU / 200 GB disk) lives in `web/services/vms/machineSpec.ts`; `web/tests/pro-pricing.test.ts` pins the pricing copy to those constants.
The paid Cloud VM allowance lives in `web/services/vms/machineSpec.ts`: Pro allows up to 50 machines for one paid account. Team grants 50 machines per paid seat, so each paid seat adds one allowance and one shared pool. Each allowance shares 5 vCPU, 20 GB RAM, and 200 GB disk across its Cloud VMs. New provider machines start with a 32 GB disk and can grow. The VM repository persists reservations and enforces the aggregate pool under the billing-team lock, treating CPU and memory as shared ceilings and adding disk claims; the entitlement module only resolves the allowance and shared-capacity policy. `web/tests/pro-pricing.test.ts` pins the pricing copy and the provider starting disk.

## Feature flags

Expand Down
4 changes: 3 additions & 1 deletion web/app/api/vm/[id]/fork/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { forkVm, runVmWorkflow } from "../../../../../services/vms/workflows";
import { VmTimingRecorder } from "../../../../../services/vms/timings";
import { authProviderErrorResponse } from "../../../../../services/vms/authErrors";
import { vmRequestLocale } from "../../../../../services/vms/vmErrorMessages";
import {
idempotencyKeyFromRequest,
parseOptionalObjectBody,
Expand Down Expand Up @@ -94,10 +95,11 @@ export async function POST(
});
} catch (err) {
if (isVmNotFoundError(err)) return notFoundVm(id);
const response = vmCreateLikeErrorResponse(err, {
const response = await vmCreateLikeErrorResponse(err, {
operation: "fork",
planId: entitlements.planId,
retryAction: "Run `cmux vm ls`, then delete an active VM with `cmux vm rm <id>` before forking another.",
locale: vmRequestLocale(request),
});
if (response) return response;
throw err;
Expand Down
2 changes: 2 additions & 0 deletions web/app/api/vm/[id]/resize/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ export async function POST(
const stats = await runVmWorkflow(resizeVm({
userId: user.id,
billingTeamId: account.entitlements.billingTeamId,
billingPlanId: account.entitlements.planId,
teamIds: user.teamIds,
providerVmId: id,
storageMb,
maxActiveVms: account.entitlements.maxActiveVms,
}));
return jsonResponse({
id,
Expand Down
Loading
Loading