{"record":{"id":"706d57f1b5206ab9","repo":"tldraw/tldraw","slug":"options-method-get-endpoint-json-str","errorCode":null,"errorMessage":"${options.method ?? 'GET'} ${endpoint}: ${JSON.stringify(data.errors)}","messagePattern":"\\$\\{options\\.method \\?\\? 'GET'\\} \\$\\{endpoint\\}: \\$\\{JSON\\.stringify\\(data\\.errors\\)\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"internal/scripts/cloudflare/ssl-cleanup.ts","lineNumber":37,"sourceCode":"const API = 'https://api.cloudflare.com/client/v4'\n\nasync function cfApi<T>(endpoint: string, options: RequestInit = {}, attempt = 0): Promise<T> {\n\tconst res = await fetch(`${API}${endpoint}`, {\n\t\t...options,\n\t\theaders: { Authorization: `Bearer ${env.CLOUDFLARE_API_TOKEN}` },\n\t})\n\tif (res.status === 429 && attempt < 3) {\n\t\tconst waitSeconds = Number(res.headers.get('retry-after')) || 10\n\t\tnicelog(`rate limited, retrying in ${waitSeconds}s...`)\n\t\tawait new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000))\n\t\treturn cfApi(endpoint, options, attempt + 1)\n\t}\n\tif (!res.ok) {\n\t\tthrow new Error(`${options.method ?? 'GET'} ${endpoint}: ${res.status} ${res.statusText}`)\n\t}\n\tconst data = (await res.json()) as { success: boolean; errors: unknown; result: T }\n\tif (!data.success) {\n\t\tthrow new Error(`${options.method ?? 'GET'} ${endpoint}: ${JSON.stringify(data.errors)}`)\n\t}\n\treturn data.result\n}\n\n// Only an HTTP 404 (deleted PR) counts as closed; any other gh failure throws\n// so a broken token can't authorize deleting open-PR packs. Uses the gh CLI so\n// local runs reuse the developer's existing auth.\nconst prStateCache = new Map<string, string>()\nfunction prState(pr: string): string {\n\tlet state = prStateCache.get(pr)\n\tif (!state) {\n\t\ttry {\n\t\t\tstate = execFileSync('gh', ['api', `repos/tldraw/tldraw/pulls/${pr}`, '--jq', '.state'], {\n\t\t\t\tencoding: 'utf-8',\n\t\t\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\t\t}).trim()\n\t\t} catch (err: any) {\n\t\t\tif (String(err.stderr).includes('HTTP 404')) {","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/tldraw/tldraw/blob/b31086b44731a7d1d9d46be4163ac8ef7417321d/internal/scripts/cloudflare/ssl-cleanup.ts#L19-L55","documentation":"Thrown by cfApi in ssl-cleanup.ts when the HTTP response was OK (2xx) but the Cloudflare API envelope returned success:false with an errors array. The method and endpoint are interpolated (supports non-GET), along with the JSON-stringified errors.","triggerScenarios":"A cfApi call (GET or DELETE) to a Cloudflare SSL/zones endpoint where the transport succeeded but Cloudflare returned a logical failure envelope — e.g. trying to DELETE a cert pack that is already in pending_deletion, or referencing a pack/zone id that the token's permissions don't cover.","commonSituations":"DELETE on an already-deleted cert pack returns success:false; referencing a stale pack id after a partial run; token scoped to read but envelope returned for a write; Cloudflare API logical rejection of the operation.","solutions":["Read the interpolated errors JSON for the Cloudflare error code and message.","For DELETE failures on packs that no longer exist, the script already counts them as 'failed' and exits non-zero; rerun skips processed packs via the Set so it converges.","Match the error code to Cloudflare SSL API docs and adjust the operation or token scopes.","If the error is transient (ongoing deletion), rerun the script."],"exampleFix":"// before\n//   DELETE /zones/.../certificate_packs/abc: [{\"code\":1000,\"message\":\"...\"}]\n//\n// after: rerun — processed packs are skipped, the stale pack resolves\n//   yarn tsx internal/scripts/cloudflare/ssl-cleanup.ts tldraw.com --delete","handlingStrategy":"try-catch","validationCode":"// Pre-check the envelope before the script's own check, to branch gracefully\nconst data = (await res.json()) as { success: boolean; errors: unknown; result: unknown }\nif (!data.success) {\n  // inspect data.errors: if 'already deleted/pending', treat as no-op instead of failure\n}","typeGuard":"function isCloudflareEnvelope<T>(data: unknown): data is { success: boolean; errors: unknown; result: T } {\n  return typeof data === 'object' && data !== null && typeof (data as any).success === 'boolean'\n}","tryCatchPattern":"try {\n  await cfApi(endpoint, { method: 'DELETE' })\n} catch (e) {\n  if (e instanceof Error && e.message.includes('JSON.stringify')) {\n    // parse errors; if pack already deleted, count as success; else record failure\n  }\n  throw e\n}","preventionTips":["Treat 'already deleted' / 'pending_deletion' envelope errors as no-ops rather than failures to let the script converge.","Log the Cloudflare error codes to classify recurring logical failures.","Rerun the script — the processed Set skips already-handled packs so it makes forward progress.","Confirm token scopes match the documented Zone:Read + SSL and Certificates:Edit."],"tags":["network","cloudflare-api","ssl","internal-scripts","api-envelope"],"backgroundTag":null,"analyzedSha":"b31086b44731a7d1d9d46be4163ac8ef7417321d","analyzedAt":"2026-08-12T17:05:39.947Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}