tldraw/tldraw · error · Error
gh api failed for PR #${pr}: ${err.stderr ?? err}
Error message
gh api failed for PR #${pr}: ${err.stderr ?? err} What it means
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.
Source
Thrown at internal/scripts/cloudflare/dns-check.ts:42
const data = (await res.json()) as { success: boolean; errors: unknown; result: T }
if (!data.success) throw new Error(`GET ${endpoint}: ${JSON.stringify(data.errors)}`)
return data.result
}
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')) {
state = 'closed'
} else {
throw new Error(`gh api failed for PR #${pr}: ${err.stderr ?? err}`)
}
}
prStateCache.set(pr, state)
}
return state
}
interface DnsRecord {
type: string
name: string
content: string
proxied: boolean
}
async function main() {
const zones = await cfApi<{ id: string }[]>(`/zones?name=${zoneName}`)
if (!zones[0]) throw new Error(`zone not found: ${zoneName}`)
const zoneId = zones[0].idView on GitHub (pinned to b31086b447)
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).
Example fix
// before // gh api failed for PR #123: HTTP 401 -- bad credentials // // after // gh auth login # authenticate with tldraw/tldraw access // yarn tsx internal/scripts/cloudflare/dns-check.ts tldraw.com
Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure gh is authed before running the script
import { execFileSync } from 'child_process'
function ghIsAuthed(): boolean {
try {
execFileSync('gh', ['auth', 'status'], { stdio: ['ignore', 'pipe', 'pipe'] })
return true
} catch {
return false
}
}
// if (!ghIsAuthed()) { console.error('run: gh auth login'); process.exit(1) } Type guard
null
Try / catch
try {
prState(prNum)
} catch (e) {
if (e instanceof Error && e.message.startsWith('gh api failed for PR')) {
// check auth, rate limit; fail safe (do NOT treat as closed)
}
throw e
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- gh api failed for PR #${pr}: ${err.stderr ?? err}
- GET ${endpoint}: ${res.status} ${res.statusText}
- GET ${endpoint}: ${JSON.stringify(data.errors)}
- zone not found: ${zoneName}
- ${options.method ?? 'GET'} ${endpoint}: ${res.status} ${res.
AI-assisted analysis of tldraw/tldraw@b31086b447 (2026-08-12).
Data as JSON: /api/errors/915a58cf5734df3a.
Report an issue: GitHub.