{"record":{"id":"8d0de6d25051a698","repo":"koala73/worldmonitor","slug":"cloudflare-method-path-did-not-complete-a-write-may-still","errorCode":null,"errorMessage":"Cloudflare ${method} ${path} did not complete (a write may still have landed): ${error.message}","messagePattern":"Cloudflare (.+?) (.+?) did not complete \\(a write may still have landed\\): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/cloudflare-cache-rule.mjs","lineNumber":502,"sourceCode":") {\n  // A hung API call must not park an `--apply` between the read and the write\n  // forever; fail loudly instead so the operator can retry against a fresh read.\n  let response;\n  try {\n    response = await fetchImpl(`${CLOUDFLARE_API}${path}`, {\n      method,\n      headers: {\n        Authorization: `Bearer ${token}`,\n        'User-Agent': USER_AGENT,\n        ...(body ? { 'Content-Type': 'application/json' } : {}),\n      },\n      ...(body ? { body: JSON.stringify(body) } : {}),\n      signal: AbortSignal.timeout(timeoutMs),\n    });\n  } catch (error) {\n    // A timeout or transport failure on a write is ambiguous — the write may\n    // still have landed — so name the request the operator has to re-read for.\n    throw new Error(`Cloudflare ${method} ${path} did not complete (a write may still have landed): ${error.message}`);\n  }\n  const payload = await response.json().catch(() => null);\n  if (!response.ok || !payload?.success) {\n    const detail = JSON.stringify(payload?.errors ?? payload ?? response.statusText);\n    throw new Error(`Cloudflare ${method} ${path} failed (${response.status}): ${detail}`);\n  }\n  return payload.result;\n}\n\nexport async function resolveZoneId(token, { env = process.env, fetchImpl } = {}) {\n  if (env.CLOUDFLARE_ZONE_ID) {\n    // Never take the id on trust. The credential that actually runs this locally\n    // is account-wide, so a stale or mistyped id would aim every write at another\n    // zone's cache rules — and the script would report success.\n    const zone = await cloudflareRequest(`/zones/${encodeURIComponent(env.CLOUDFLARE_ZONE_ID)}`, {\n      token,\n      fetchImpl,\n    });","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/cloudflare-cache-rule.mjs#L484-L520","documentation":"cloudflareRequest wraps fetch calls to the Cloudflare API. When a request times out (AbortSignal.timeout) or fails at the transport level, it rethrows with the method, path, and original error. The message deliberately warns that for a write, the operation may still have landed server-side despite the client not seeing a response, so the operator must re-read state before retrying.","triggerScenarios":"The fetch to /zones/.../rulesets/... times out via AbortSignal.timeout(timeoutMs), the connection is reset, DNS fails, or TLS/transport errors occur mid-request.","commonSituations":"Slow or flaky network to api.cloudflare.com, an overly tight timeoutMs, corporate proxy interference, or a Cloudflare API incident causing long latency on a PATCH/POST write.","solutions":["Before retrying a write, GET the target ruleset/rule to check whether the write already landed.","Increase timeoutMs or fix network connectivity if timeouts are frequent.","Retry idempotent reads freely; for writes, make the retry idempotent (same body) only after confirming current state.","Check Cloudflare API status if many requests time out at once."],"exampleFix":"// before\nawait cloudflareRequest(path, { method: 'PATCH', body, timeoutMs: 2000 });\n// after\ntry {\n  await cloudflareRequest(path, { method: 'PATCH', body, timeoutMs: 10000 });\n} catch (e) {\n  const current = await cloudflareRequest(readPath, { method: 'GET' });\n  if (!matchesDesiredState(current)) throw e;\n}","handlingStrategy":"try-catch","validationCode":"// pre-flight connectivity probe before writes\nconst probe = await fetch('https://api.cloudflare.com/client/v4/user/tokens/verify', { signal: AbortSignal.timeout(5000) });\nif (!probe.ok) throw new Error('Cloudflare API unreachable; skipping write');","typeGuard":null,"tryCatchPattern":"try {\n  await cloudflareRequest(path, { token, method: 'PATCH', body, fetchImpl });\n} catch (e) {\n  if (e.message.includes('did not complete (a write may still have landed)')) {\n    const current = await cloudflareRequest(readPath, { token, fetchImpl }); // re-read before retry\n    if (!stateMatchesIntent(current)) throw e;\n    return; // write had landed\n  }\n  throw e;\n}","preventionTips":["Set generous but bounded timeoutMs for write operations.","Always re-read state after an ambiguous write failure before retrying.","Make write retries idempotent (same body produces same desired state).","Monitor Cloudflare API status pages during incidents."],"tags":["cloudflare","network","timeout","ambiguity"],"backgroundTag":"request-timeout","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}