{"record":{"id":"915a58cf5734df3a","repo":"tldraw/tldraw","slug":"gh-api-failed-for-pr-pr-err-stderr-err","errorCode":null,"errorMessage":"gh api failed for PR #${pr}: ${err.stderr ?? err}","messagePattern":"gh api failed for PR #(.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"internal/scripts/cloudflare/dns-check.ts","lineNumber":42,"sourceCode":"\tconst data = (await res.json()) as { success: boolean; errors: unknown; result: T }\n\tif (!data.success) throw new Error(`GET ${endpoint}: ${JSON.stringify(data.errors)}`)\n\treturn data.result\n}\n\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')) {\n\t\t\t\tstate = 'closed'\n\t\t\t} else {\n\t\t\t\tthrow new Error(`gh api failed for PR #${pr}: ${err.stderr ?? err}`)\n\t\t\t}\n\t\t}\n\t\tprStateCache.set(pr, state)\n\t}\n\treturn state\n}\n\ninterface DnsRecord {\n\ttype: string\n\tname: string\n\tcontent: string\n\tproxied: boolean\n}\n\nasync function main() {\n\tconst zones = await cfApi<{ id: string }[]>(`/zones?name=${zoneName}`)\n\tif (!zones[0]) throw new Error(`zone not found: ${zoneName}`)\n\tconst zoneId = zones[0].id","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/tldraw/tldraw/blob/b31086b44731a7d1d9d46be4163ac8ef7417321d/internal/scripts/cloudflare/dns-check.ts#L24-L60","documentation":"Thrown by prState in dns-check.ts when the gh CLI call to fetch a PR's state fails with an error whose stderr does not contain 'HTTP 404'. A 404 is intentionally treated as 'closed' (deleted PR); any other failure is re-thrown so a broken gh auth cannot misclassify an open PR as closed.","triggerScenarios":"The dns-check script resolves each pr-NNNN-* DNS record to a PR number and calls gh api repos/tldraw/tldraw/pulls/NNN; if gh exits non-zero for any reason other than a 404 (auth failure, network error, rate limit, 500), this throws.","commonSituations":"gh CLI is not authenticated (gh auth login not run / token expired); GH_TOKEN has no scope for tldraw/tldraw; GitHub API rate limit; transient network failure; the PR number was malformed from the record name.","solutions":["Run 'gh auth status' and 'gh auth login' to ensure the CLI is authenticated for tldraw/tldraw.","Read the interpolated stderr to identify the exact gh failure (rate limit, auth, network).","If rate-limited, wait and rerun; the prStateCache means reruns skip already-resolved PRs.","Verify the PR number parsed from the DNS record name is a valid integer (the regex /^pr-(\\d+)-/ should guarantee this)."],"exampleFix":"// before\n//   gh api failed for PR #123: HTTP 401 -- bad credentials\n//\n// after\n//   gh auth login   # authenticate with tldraw/tldraw access\n//   yarn tsx internal/scripts/cloudflare/dns-check.ts tldraw.com","handlingStrategy":"try-catch","validationCode":"// Ensure gh is authed before running the script\nimport { execFileSync } from 'child_process'\nfunction ghIsAuthed(): boolean {\n  try {\n    execFileSync('gh', ['auth', 'status'], { stdio: ['ignore', 'pipe', 'pipe'] })\n    return true\n  } catch {\n    return false\n  }\n}\n// if (!ghIsAuthed()) { console.error('run: gh auth login'); process.exit(1) }","typeGuard":"null","tryCatchPattern":"try {\n  prState(prNum)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('gh api failed for PR')) {\n    // check auth, rate limit; fail safe (do NOT treat as closed)\n  }\n  throw e\n}","preventionTips":["Run 'gh auth status' before invoking the script to confirm authentication for tldraw/tldraw.","Set GH_TOKEN in CI from a secret with appropriate repo read scope.","Never treat a non-404 gh failure as 'closed' — that would misclassify open PRs (the script is correctly strict).","Retry on transient network/rate-limit errors, leveraging prStateCache to skip resolved PRs."],"tags":["github-api","gh-cli","dns","internal-scripts","auth"],"backgroundTag":null,"analyzedSha":"b31086b44731a7d1d9d46be4163ac8ef7417321d","analyzedAt":"2026-08-12T17:05:39.947Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}