{"record":{"id":"bf71f635030cce90","repo":"affaan-m/ECC","slug":"github-graphql-returned-errors","errorCode":null,"errorMessage":"GitHub GraphQL returned errors","messagePattern":"GitHub GraphQL returned errors","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/discord/release-announce.mjs","lineNumber":37,"sourceCode":"async function request(url, options = {}, attempts = 3) {\n  for (let attempt = 1; attempt <= attempts; attempt += 1) {\n    const response = await fetch(url, options);\n    if (response.status !== 429 || attempt === attempts) return response;\n    const data = await response.json().catch(() => ({}));\n    await sleep(Math.min(Number(data.retry_after || 1) * 1000 + 250, 10_000));\n  }\n  throw new Error('request retry budget exhausted');\n}\n\nasync function githubGraphql(query, variables) {\n  const response = await request('https://api.github.com/graphql', {\n    method: 'POST',\n    headers: { Authorization: `Bearer ${env.GITHUB_TOKEN}`, 'Content-Type': 'application/json' },\n    body: JSON.stringify({ query, variables }),\n  });\n  if (!response.ok) throw new Error(`GitHub GraphQL request failed (${response.status})`);\n  const payload = await response.json();\n  if (payload.errors) throw new Error('GitHub GraphQL returned errors');\n  return payload.data;\n}\n\nasync function releaseFromGitHub() {\n  const [owner, repo] = env.GITHUB_REPOSITORY.split('/');\n  const tag = env.RELEASE_TAG || env.GITHUB_REF_NAME;\n  const response = await request(`https://api.github.com/repos/${owner}/${repo}/releases/tags/${encodeURIComponent(tag)}`, {\n    headers: { Authorization: `Bearer ${env.GITHUB_TOKEN}`, Accept: 'application/vnd.github+json' },\n  });\n  if (!response.ok) throw new Error(`release lookup failed (${response.status})`);\n  return response.json();\n}\n\nasync function createOrFindReleaseDiscussion() {\n  const release = await releaseFromGitHub();\n  const [owner, name] = env.GITHUB_REPOSITORY.split('/');\n  const marker = releaseMarker(release.tag_name);\n  const data = await githubGraphql(","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/discord/release-announce.mjs#L19-L55","documentation":"githubGraphql wraps GitHub's GraphQL API POST and throws this error when the HTTP response is OK but the JSON payload contains an `errors` array. GitHub returns 200 with `errors` for query-level problems (bad query syntax, missing permissions, invalid node IDs, partial failures). The status check alone cannot catch these, so this guard surfaces them as a normal Error.","triggerScenarios":"Any call to githubGraphql where GitHub accepts the request (HTTP 2xx) but the response body has a truthy `payload.errors` array — e.g. malformed GraphQL query, missing `read:discussion` scope for a discussion query, invalid repositoryId/categoryId in a createDiscussion mutation.","commonSituations":"GITHUB_TOKEN lacks the `repo` or `read:discussion` scope required for discussion queries; a renamed/removed discussion node ID passed to a query; typo in a GraphQL field or variable declaration; rate-limit or abuse-detection errors returned in-band.","solutions":["Log `payload.errors` (temporarily change the throw to include JSON.stringify(payload.errors)) to read GitHub's specific message","Verify GITHUB_TOKEN has `repo`, `read:discussion` (and `write:discussion` for mutations) scopes","Validate the GraphQL query in GitHub's GraphQL explorer with the same token","Check for rate-limit/abuse errors in the errors array and add retry with backoff"],"exampleFix":"// before\nif (payload.errors) throw new Error('GitHub GraphQL returned errors');\n// after\nif (payload.errors) throw new Error(`GitHub GraphQL returned errors: ${JSON.stringify(payload.errors)}`);","handlingStrategy":"try-catch","validationCode":"// preflight: verify token scopes with a cheap query\nconst check = await fetch('https://api.github.com/graphql', {\n  method: 'POST',\n  headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ query: 'query{viewer{login}}' }),\n});\nconst body = await check.json();\nif (body.errors || !body.data?.viewer?.login) throw new Error(`GraphQL preflight failed: ${JSON.stringify(body.errors)}`);","typeGuard":"function hasGraphqlErrors(payload) {\n  return Array.isArray(payload?.errors) && payload.errors.length > 0;\n}","tryCatchPattern":"try {\n  const data = await githubGraphql(query, variables);\n} catch (err) {\n  if (String(err.message).includes('GitHub GraphQL returned errors')) {\n    console.error('GraphQL errors:', err.message); // inspect scopes/ids/query\n  }\n  throw err;\n}","preventionTips":["Always test queries/mutations in GitHub's GraphQL explorer with the same token used in CI","Include required scopes (repo, read:discussion, write:discussion) in the fine-grained PAT or GitHub App","Include error details in the thrown message (JSON.stringify(payload.errors)) for diagnosability","Handle rate-limit errors explicitly with retry + backoff"],"tags":["graphql","github-api","api-error"],"backgroundTag":"api-error-response","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}