musistudio/claude-code-router · error · Error

Cloudflare Pages publishing requires a runtime with fetch, F

Error message

Cloudflare Pages publishing requires a runtime with fetch, FormData, and Blob support.

What it means

Guard error thrown before attempting a Cloudflare Pages direct-upload deployment because the current JavaScript runtime lacks FormData or Blob constructors. The deployment flow builds a multipart form with FormData/Blob, which requires a modern browser-like or Node 18+ runtime.

Source

Thrown at packages/electron/bundled-plugins/claude-design/index.cjs:5635

    headers: {
      "content-type": "application/json"
    },
    method: "POST"
  });

  return { filesCached: 0, filesUploaded: files.length, skipped: false };
}

function cloudflarePagesUploadToken(payload) {
  if (isRecord(payload?.result)) {
    return stringValue(payload.result.jwt) || stringValue(payload.result.token) || stringValue(payload.result.value);
  }
  return stringValue(payload?.result) || stringValue(payload?.jwt) || stringValue(payload?.token);
}

async function createCloudflarePagesDeployment(config, projectName, files, session, body) {
  if (typeof FormData !== "function" || typeof Blob !== "function") {
    throw new Error("Cloudflare Pages publishing requires a runtime with fetch, FormData, and Blob support.");
  }
  const branch = stringValue(body?.branch) || stringValue(body?.git_branch) || config.branch || "main";
  const commitSha = claudeShipDeploymentCommitSha(files);
  const form = new FormData();
  form.set("branch", branch);
  form.set("commit_dirty", "false");
  form.set("commit_hash", commitSha);
  form.set("commit_message", `Publish ${session.title || session.id} from Claude Ship`);
  form.set("pages_build_output_dir", ".");
  form.set("manifest", JSON.stringify(Object.fromEntries(files.map((file) => [file.path, file.hash]))));
  for (const file of files) {
    form.set(file.hash, new Blob([file.body], { type: file.contentType }), file.path);
    if (file.path.startsWith("_")) {
      form.set(file.path, new Blob([file.body], { type: file.contentType }), file.path);
    }
  }

  const accountId = encodeURIComponent(config.accountId);

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Run in Node 18+ or a browser/Electron renderer context where FormData and Blob are global
  2. If in Node <18, import undici's FormData/Blob and assign them to globals before calling
  3. Upgrade the Electron/host app so the runtime provides fetch/FormData/Blob
  4. Add a runtime capability check with a clear message at startup instead of at publish time

Example fix

// before
// Node 16: FormData undefined -> throws
await createCloudflarePagesDeployment(config, name, files, session, body);

// after
import { FormData, Blob } from 'undici';
globalThis.FormData ??= FormData;
globalThis.Blob ??= Blob;
await createCloudflarePagesDeployment(config, name, files, session, body);
Defensive patterns

Strategy: type-guard

Validate before calling

const canDeploy = typeof fetch === 'function' && typeof FormData === 'function' && typeof Blob === 'function';
if (!canDeploy) await import('undici').then(u => Object.assign(globalThis, { FormData: u.FormData, Blob: u.Blob }));

Type guard

function supportsDirectUpload(): boolean {
  return typeof fetch === 'function' && typeof FormData === 'function' && typeof Blob === 'function';
}

Try / catch

catch (e) { if (e.message.includes('fetch, FormData, and Blob')) { polyfill or upgrade runtime, then retry once } else throw e; }

Prevention

When it happens

Trigger: Running createCloudflarePagesDeployment in Node <18, a restricted sandbox/renderer without FormData, or a polyfill-free environment where typeof FormData !== 'function'.

Common situations: Bundling the connector for an older Electron/Node runtime, running in a worker where FormData is not global, or a bundler stripping/stubbing browser globals.

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/c5a0a329dfdafe7d. Report an issue: GitHub.