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

  1. Read the interpolated errors JSON for the Cloudflare error code and message.
  2. 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.
  3. Match the error code to Cloudflare SSL API docs and adjust the operation or token scopes.
  4. 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

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


AI-assisted analysis of tldraw/tldraw@b31086b447 (2026-08-12). Data as JSON: /api/errors/706d57f1b5206ab9. Report an issue: GitHub.