tldraw/tldraw · error · Error
${options.method ?? 'GET'} ${endpoint}: ${JSON.stringify(dat
Error message
${options.method ?? 'GET'} ${endpoint}: ${JSON.stringify(data.errors)} What it means
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.
Source
Thrown at internal/scripts/cloudflare/ssl-cleanup.ts:37
const API = 'https://api.cloudflare.com/client/v4'
async function cfApi<T>(endpoint: string, options: RequestInit = {}, attempt = 0): Promise<T> {
const res = await fetch(`${API}${endpoint}`, {
...options,
headers: { Authorization: `Bearer ${env.CLOUDFLARE_API_TOKEN}` },
})
if (res.status === 429 && attempt < 3) {
const waitSeconds = Number(res.headers.get('retry-after')) || 10
nicelog(`rate limited, retrying in ${waitSeconds}s...`)
await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000))
return cfApi(endpoint, options, attempt + 1)
}
if (!res.ok) {
throw new Error(`${options.method ?? 'GET'} ${endpoint}: ${res.status} ${res.statusText}`)
}
const data = (await res.json()) as { success: boolean; errors: unknown; result: T }
if (!data.success) {
throw new Error(`${options.method ?? 'GET'} ${endpoint}: ${JSON.stringify(data.errors)}`)
}
return data.result
}
// Only an HTTP 404 (deleted PR) counts as closed; any other gh failure throws
// so a broken token can't authorize deleting open-PR packs. Uses the gh CLI so
// local runs reuse the developer's existing auth.
const prStateCache = new Map<string, string>()
function prState(pr: string): string {
let state = prStateCache.get(pr)
if (!state) {
try {
state = execFileSync('gh', ['api', `repos/tldraw/tldraw/pulls/${pr}`, '--jq', '.state'], {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'pipe'],
}).trim()
} catch (err: any) {
if (String(err.stderr).includes('HTTP 404')) {View on GitHub (pinned to b31086b447)
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.
Example fix
// before
// DELETE /zones/.../certificate_packs/abc: [{"code":1000,"message":"..."}]
//
// after: rerun — processed packs are skipped, the stale pack resolves
// yarn tsx internal/scripts/cloudflare/ssl-cleanup.ts tldraw.com --delete Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check the envelope before the script's own check, to branch gracefully
const data = (await res.json()) as { success: boolean; errors: unknown; result: unknown }
if (!data.success) {
// inspect data.errors: if 'already deleted/pending', treat as no-op instead of failure
} Type guard
function isCloudflareEnvelope<T>(data: unknown): data is { success: boolean; errors: unknown; result: T } {
return typeof data === 'object' && data !== null && typeof (data as any).success === 'boolean'
} Try / catch
try {
await cfApi(endpoint, { method: 'DELETE' })
} catch (e) {
if (e instanceof Error && e.message.includes('JSON.stringify')) {
// parse errors; if pack already deleted, count as success; else record failure
}
throw e
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- GET ${endpoint}: ${JSON.stringify(data.errors)}
- ${options.method ?? 'GET'} ${endpoint}: ${res.status} ${res.
- GET ${endpoint}: ${res.status} ${res.statusText}
- zone not found: ${zoneName}
- zone not found: ${zoneName}
AI-assisted analysis of tldraw/tldraw@b31086b447 (2026-08-12).
Data as JSON: /api/errors/706d57f1b5206ab9.
Report an issue: GitHub.