{"record":{"id":"fbefbbd96a36ff58","repo":"nexu-io/open-design","slug":"cloudflare-returned-a-non-json-response","errorCode":null,"errorMessage":"Cloudflare returned a non-JSON response.","messagePattern":"Cloudflare returned a non-JSON response\\.","errorType":"http","errorClass":"DeployError","httpStatus":502,"severity":"error","filePath":"apps/daemon/src/deploy.ts","lineNumber":1864,"sourceCode":"function cloudflareHeaders(config: DeployConfig, extra: Record<string, string> = {}) {\n  return {\n    Authorization: `Bearer ${config.token}`,\n    ...extra,\n  };\n}\n\nfunction cloudflareAssetHeaders(token: string, extra: Record<string, string> = {}) {\n  return {\n    Authorization: `Bearer ${token}`,\n    ...extra,\n  };\n}\n\nasync function readCloudflareJson(resp: Response): Promise<JsonObject> {\n  try {\n    return await resp.json() as JsonObject;\n  } catch {\n    throw new DeployError('Cloudflare returned a non-JSON response.', resp.status || 502);\n  }\n}\n\nasync function fetchCloudflarePaginatedResult(config: DeployConfig, buildUrl: (page: number, perPage: number) => string, fallback: string, options: { perPage?: number } = {}) {\n  const results: JsonObject[] = [];\n  const perPage = options.perPage || CLOUDFLARE_API_PAGE_SIZE;\n  for (let page = 1; page <= CLOUDFLARE_API_MAX_PAGES; page += 1) {\n    const resp = await fetch(buildUrl(page, perPage), {\n      headers: cloudflareHeaders(config),\n    });\n    const json = await readCloudflareJson(resp);\n    if (!resp.ok || json?.success === false) {\n      throw cloudflareError(json, resp.status, fallback);\n    }\n    const pageItems = Array.isArray(json?.result) ? json.result : [];\n    results.push(...pageItems);\n    if (!shouldFetchNextCloudflarePage(json?.result_info, page, perPage, pageItems.length)) break;\n  }","sourceCodeStart":1846,"sourceCodeEnd":1882,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/deploy.ts#L1846-L1882","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"async function assertJsonResponse(resp: Response): Promise<void> {\n  const ct = resp.headers.get('content-type') ?? '';\n  if (!ct.includes('application/json')) {\n    throw new Error(`Expected JSON from Cloudflare, got ${ct || 'unknown'} (status ${resp.status}).`);\n  }\n}\n\nawait assertJsonResponse(resp);","typeGuard":"function isCloudflareJsonError(err: unknown): boolean {\n  return err instanceof DeployError && /non-JSON response/i.test(err.message);\n}","tryCatchPattern":"async function cloudflareJsonWithRetry(build: () => Promise<Response>, attempts = 3): Promise<JsonObject> {\n  for (let i = 1; i <= attempts; i++) {\n    try {\n      return await readCloudflareJson(await build());\n    } catch (err) {\n      if (err instanceof DeployError && /non-JSON response/i.test(err.message) && i < attempts) {\n        await new Promise((r) => setTimeout(r, 1000 * i));\n        continue;\n      }\n      throw err;\n    }\n  }\n  throw new Error('unreachable');\n}","preventionTips":["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."],"tags":["cloudflare","deploy","network","upstream"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}