nexu-io/open-design · error · DeployError
cloudflare_zone_mismatch
cloudflare_zone_mismatch
Error message
Cloudflare zone selection no longer matches the selected domain.
What it means
Thrown by validateCloudflarePagesDeploySelection in apps/daemon/src/deploy.ts:601 as `DeployError('Cloudflare zone selection no longer matches the selected domain.', 400, { errorCode: 'cloudflare_zone_mismatch' })`. After fetching the live zone by id from Cloudflare and normalizing its name, if it differs from the selection's stored zoneName, the selection is treated as stale/inconsistent and rejected. The `errorCode` lets the UI present this distinctly from generic 400s.
Source
Thrown at apps/daemon/src/deploy.ts:601
zoneName,
domainPrefix,
hostname: `${domainPrefix}.${zoneName}`,
};
}
async function validateCloudflarePagesDeploySelection(config: DeployConfig, selection: CloudflarePagesDeploySelection | null): Promise<CloudflarePagesDeploySelection | null> {
if (!selection) return null;
const resp = await fetch(`${CLOUDFLARE_API}/zones/${encodeURIComponent(selection.zoneId)}`, {
headers: cloudflareHeaders(config),
});
const json = await readCloudflareJson(resp);
if (!resp.ok || json?.success === false) {
throw cloudflareError(json, resp.status, 'Cloudflare zone lookup failed.');
}
const zone = json?.result ?? json;
const zoneName = normalizeCloudflareZoneName(zone?.name);
if (!zoneName || zoneName !== selection.zoneName) {
throw new DeployError('Cloudflare zone selection no longer matches the selected domain.', 400, {
errorCode: 'cloudflare_zone_mismatch',
});
}
if (zone?.status && zone.status !== 'active') {
throw new DeployError('Cloudflare custom domains require an active zone.', 400, {
errorCode: 'cloudflare_zone_inactive',
});
}
if (zone?.type && zone.type !== 'full') {
throw new DeployError('Cloudflare custom domains require a full DNS zone.', 400, {
errorCode: 'cloudflare_zone_not_full',
});
}
return { ...selection, zoneName };
}
async function setupCloudflarePagesCustomDomain({ config, projectId, selection, pagesDevUrl, priorMetadata }: { config: DeployConfig; projectId: string; selection: CloudflarePagesDeploySelection; pagesDevUrl: string; priorMetadata?: JsonObject | undefined }) {
if (!config.projectName) throw new DeployError('Cloudflare Pages project name could not be generated.', 400);View on GitHub (pinned to 5be4028344)
Solutions
- Re-fetch zones via listCloudflarePagesZones and let the user re-select the zone to refresh both zoneId and zoneName.
- Drive the selection from a single source (the picker) instead of stitching zoneId and zoneName from different states.
- Handle errorCode 'cloudflare_zone_mismatch' in the UI by clearing the saved selection and reopening the picker.
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
function isZoneMismatch(err: unknown): boolean {
return err instanceof DeployError && err.status === 400
&& (err as any).details?.errorCode === 'cloudflare_zone_mismatch';
} Try / catch
try {
await deployToCloudflarePages({ config, files, projectId, cloudflarePages: selection });
} catch (err) {
if (isZoneMismatch(err)) {
return res.status(400).json({
error: 'The selected zone changed. Re-select the domain and retry.',
errorCode: 'cloudflare_zone_mismatch',
});
}
throw err;
} Prevention
- Refresh zones via listCloudflarePagesZones whenever the picker is opened, so selections use current data.
- Bind zoneId and zoneName to the same picker event so they never desync.
- On a mismatch, clear the persisted selection and force a fresh pick rather than retrying blindly.
When it happens
Trigger: A selection carries a zoneId+zoneName pair, the zone lookup succeeds (resp.ok and success !== false), but `zone.name` from the API normalized does not equal `selection.zoneName`. Typically the zone was renamed, the zoneId now points to a different zone, or the selection was crafted from stale cached data.
Common situations: User picked a zone earlier, then the zone was renamed/deleted/recreated in Cloudflare; the selection was persisted across a session and the zone list changed; a different account's zoneId leaked into the selection; frontend cached an old zone name.
Related errors
- Cloudflare zone is required for a custom domain.
- Select a valid Cloudflare domain for the custom domain.
- cloudflare_zone_inactive
- cloudflare_zone_not_full
- Enter a valid subdomain prefix, for example "demo".
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/6b594b7d4452b4f8.
Report an issue: GitHub.