nexu-io/open-design · error · DeployError
Cloudflare returned a non-JSON response.
Error message
Cloudflare returned a non-JSON response.
What it means
readCloudflareJson calls resp.json(); if the response body is not valid JSON the parse throws and the daemon wraps it as a DeployError with the original HTTP status (or 502 if no status). This means Cloudflare returned an HTML error page, an empty body, or plain-text gateway text instead of the expected JSON envelope.
Source
Thrown at apps/daemon/src/deploy.ts:1864
function cloudflareHeaders(config: DeployConfig, extra: Record<string, string> = {}) {
return {
Authorization: `Bearer ${config.token}`,
...extra,
};
}
function cloudflareAssetHeaders(token: string, extra: Record<string, string> = {}) {
return {
Authorization: `Bearer ${token}`,
...extra,
};
}
async function readCloudflareJson(resp: Response): Promise<JsonObject> {
try {
return await resp.json() as JsonObject;
} catch {
throw new DeployError('Cloudflare returned a non-JSON response.', resp.status || 502);
}
}
async function fetchCloudflarePaginatedResult(config: DeployConfig, buildUrl: (page: number, perPage: number) => string, fallback: string, options: { perPage?: number } = {}) {
const results: JsonObject[] = [];
const perPage = options.perPage || CLOUDFLARE_API_PAGE_SIZE;
for (let page = 1; page <= CLOUDFLARE_API_MAX_PAGES; page += 1) {
const resp = await fetch(buildUrl(page, perPage), {
headers: cloudflareHeaders(config),
});
const json = await readCloudflareJson(resp);
if (!resp.ok || json?.success === false) {
throw cloudflareError(json, resp.status, fallback);
}
const pageItems = Array.isArray(json?.result) ? json.result : [];
results.push(...pageItems);
if (!shouldFetchNextCloudflarePage(json?.result_info, page, perPage, pageItems.length)) break;
}View on GitHub (pinned to 5be4028344)
Solutions
- Retry the request after a short wait; most non-JSON responses are transient edge errors.
- Check the Cloudflare status page for an active incident.
- Verify the API token is still valid and not revoked.
- Disable any intercepting proxy for api.cloudflare.com.
Defensive patterns
Strategy: retry
Validate before calling
async function assertJsonResponse(resp: Response): Promise<void> {
const ct = resp.headers.get('content-type') ?? '';
if (!ct.includes('application/json')) {
throw new Error(`Expected JSON from Cloudflare, got ${ct || 'unknown'} (status ${resp.status}).`);
}
}
await assertJsonResponse(resp); Type guard
function isCloudflareJsonError(err: unknown): boolean {
return err instanceof DeployError && /non-JSON response/i.test(err.message);
} Try / catch
async function cloudflareJsonWithRetry(build: () => Promise<Response>, attempts = 3): Promise<JsonObject> {
for (let i = 1; i <= attempts; i++) {
try {
return await readCloudflareJson(await build());
} catch (err) {
if (err instanceof DeployError && /non-JSON response/i.test(err.message) && i < attempts) {
await new Promise((r) => setTimeout(r, 1000 * i));
continue;
}
throw err;
}
}
throw new Error('unreachable');
} Prevention
- Inspect response Content-Type before parsing when calling Cloudflare directly.
- Retry non-JSON responses with backoff; they are usually transient edge errors.
- Keep the Cloudflare token fresh so auth walls do not serve HTML instead of JSON.
When it happens
Trigger: Cloudflare's edge returns a non-JSON body: a 5xx HTML error page, a Cloudflare WAF/challenge interstitial, a login/auth wall, or an empty body from a gateway timeout. Any of these makes resp.json() throw.
Common situations: Transient Cloudflare outage; corporate proxy intercepting api.cloudflare.com and returning HTML; token revoked so Cloudflare serves an auth page; DNS/CDN-level incident returning a static page.
Related errors
- Cloudflare reported an unknown asset hash: ${hash}
- Vercel returned a non-JSON response.
- Cloudflare API token is required.
- Cloudflare account ID is required.
- Cloudflare zone is required for a custom domain.
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/fbefbbd96a36ff58.
Report an issue: GitHub.