Skip to content

fix(clusters): explain the two saves a region change takes instead of failing the update - #1849

Draft
dawsontoth wants to merge 2 commits into
claude/1275-region-removal-keeps-siblingsfrom
claude/1311-guide-region-swap
Draft

dawsontoth wants to merge 2 commits into
claude/1275-region-removal-keeps-siblingsfrom
claude/1311-guide-region-swap

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

⊙ Problem

Changing the region of an existing cluster fails at the API with "Cannot delete all region plans from a cluster and add new region plan in the same update" (#1311). Central manager matches a cluster's regions by name and refuses any single update that leaves every current region while adding a new one, so a move takes two saves: add the new region and save, then remove the old one. The form gave no hint of that: a user who switched their only region from US to Europe went through payment review and then got the raw API error.

Reproduced on current stage in a real browser (Playwright, local dev server, mocked API): on /#/<org>/<cluster>/edit with the cluster in US, choosing Europe left the submit button enabled with no message, and submitting sent regionPlans: [{ regionId: 'eu-1', … }], the exact update the server refuses.

❓ Your call: the issue offers two directions, guide the user or run the two saves automatically. This PR guides and blocks. Running both saves for the user would make two infrastructure-changing, separately billed updates from one click and would have to wait for the first to finish (the server refuses updates to a cluster that is not RUNNING) — worth doing only if you want that behaviour as a product decision.

❓ Your call: a tier that allows a single region (the free tier, or any plan with allowedRegionIds) cannot take the two-step path, because the form will not hold a second region there. The guidance on such a tier says the cluster can't move in place and to create a new cluster in the target region. If support would rather be contacted, or there is another path, that is a one-string change.

💡 Solution

On an edit, the form now compares the selected region names with the regions the cluster runs in now. When none of the current regions is kept, the first region field shows the steps, for example "A cluster can't move out of all its current regions in one update. Keep US, add Europe as an additional region and save, then remove US once that update has finished.", and submitting stays blocked until a current region is kept. The check mirrors only the case the server is certain to refuse, so it never blocks an update the server would accept.

🔧 Changes

Not covered: the server also counts shrinking a kept region (a smaller latency/distribution tier of the same region) as leaving it, so shrinking every current region while adding a new one is refused too. That combination is not mirrored here and still surfaces the server's error.

Stacked on #1842 (base claude/1275-region-removal-keeps-siblings); retarget to stage once that merges. PR #1780 (custom regions) rewrites refineZod and index.tsx to work on region ids, so the refinement call and the prop will need to be carried over when it rebases; the helper itself works on names and should survive unchanged.

✅ Verification

Route: component tests driving the real ClusterForm in jsdom (Radix selects opened and picked through clicks), unit tests for the helper, and a live browser check through the real edit route.

Cross-model review: two rounds, codex (graded) and gemini. Fixed from round 1: the multi-region message told the user to "remove the others" after the first save, when the regions left by then are the ones they kept; and the derivation in index.tsx had no test, so it moved into the helper with one. Rejected: explicit import extensions (this repo's imports are extensionless), the allocation of mapping at most 50 rows on each validation of the create form (the same refinement already loops over them), and treating a missing selectedPlan as multi-region (with no plan the form hides the add-region button, so the single-region message is the consistent one). The Cursor leg could not fetch over SSH and the Harper domain adjudicator failed authentication, so outside findings were triaged by hand.

Closes #1311

🤖 Generated by Anthropic Claude Code (Claude Opus 5.5); posted via @dawsontoth.

🤖 Generated with Claude Code

Related PRs: #1842 overlaps (this branch is stacked on it), #1780 overlaps (rewrites the same refinement and props for region ids)
Complexity: medium

Review-Coverage: authored=claude; ran=codex,gemini; blocked=cursor-composer(no-receipt),domain(auth); declined=cursor-grok,cursor-kimi,cursor-muse; rounds=2; full=1 @ c7381f3

Review-Attention: read ~3m (raised: degraded review) @ c7381f3

dawsontoth and others added 2 commits October 11, 2026 01:26
…pdate fails

Central manager refuses a cluster update that leaves every region the
cluster runs in while adding another ("Cannot delete all region plans from a
cluster and add new region plan in the same update"). Changing an existing
cluster's only region went all the way through payment review and then
failed with that error.

Flag that selection on an edit, under the first region, with the steps:
keep the current region, add the new one and save, then remove the old one.
A tier that allows a single region cannot do that, so it says the cluster
cannot move in place instead. Submitting stays blocked until a current
region is kept.

Refs #1311

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With several current regions the guidance said "remove the others" after the
first save, but the regions left by then are the ones the user kept. Name
those instead, skip the work entirely when creating, and test how a
cluster's plans resolve to the region names the check compares.

Refs #1311

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces client-side validation and guidance to prevent users from attempting to move a cluster out of all its current regions in a single update, which is rejected by the central manager. It adds helper functions in describeRegionSwap.ts to detect invalid region swaps and display helpful guidance messages, integrates this validation into ClusterForm, and includes comprehensive tests. Feedback on the pull request points out a potential bug where an undefined selectedPlan could incorrectly flag a plan as a single-region tier, and suggests a fix to explicitly guard the check.

Comment on lines +172 to +176
const regionSwap = describeRegionSwap({
currentRegionNames,
selectedRegionNames: data.regionPlans.map(regionPlan => regionPlan.regionName),
singleRegionTier: !selectedPlan?.priceUsd || !!selectedPlan.allowedRegionIds?.length,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When selectedPlan is undefined (e.g., during initial load or if the selected deployment/performance descriptions are temporarily invalid), !selectedPlan?.priceUsd evaluates to true. This incorrectly flags the plan as a singleRegionTier, which can display a misleading "single-region tier" error message to the user instead of a generic or no error message.

We should explicitly guard this check to ensure selectedPlan is defined before determining if it is a single-region tier.

Suggested change
const regionSwap = describeRegionSwap({
currentRegionNames,
selectedRegionNames: data.regionPlans.map(regionPlan => regionPlan.regionName),
singleRegionTier: !selectedPlan?.priceUsd || !!selectedPlan.allowedRegionIds?.length,
});
const regionSwap = describeRegionSwap({
currentRegionNames,
selectedRegionNames: data.regionPlans.map(regionPlan => regionPlan.regionName),
singleRegionTier: !!selectedPlan && (!selectedPlan.priceUsd || !!selectedPlan.allowedRegionIds?.length),
});

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