affaan-m/ECC · error · Error
GitHub GraphQL returned errors
Error message
GitHub GraphQL returned errors
What it means
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.
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
Example fix
// before
if (payload.errors) throw new Error('GitHub GraphQL returned errors');
// after
if (payload.errors) throw new Error(`GitHub GraphQL returned errors: ${JSON.stringify(payload.errors)}`); Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: verify token scopes with a cheap query
const check = await fetch('https://api.github.com/graphql', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query: 'query{viewer{login}}' }),
});
const body = await check.json();
if (body.errors || !body.data?.viewer?.login) throw new Error(`GraphQL preflight failed: ${JSON.stringify(body.errors)}`); Type guard
function hasGraphqlErrors(payload) {
return Array.isArray(payload?.errors) && payload.errors.length > 0;
} Try / catch
try {
const data = await githubGraphql(query, variables);
} catch (err) {
if (String(err.message).includes('GitHub GraphQL returned errors')) {
console.error('GraphQL errors:', err.message); // inspect scopes/ids/query
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- discussion receipt lookup failed
- announcement delivered; pending receipt cleanup requires…
- Announcements discussion category is required
- discussion lookup failed
- discussion number is invalid
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/bf71f635030cce90.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/discord/release-announce.mjs:37
async function request(url, options = {}, attempts = 3) {
for (let attempt = 1; attempt <= attempts; attempt += 1) {
const response = await fetch(url, options);
if (response.status !== 429 || attempt === attempts) return response;
const data = await response.json().catch(() => ({}));
await sleep(Math.min(Number(data.retry_after || 1) * 1000 + 250, 10_000));
}
throw new Error('request retry budget exhausted');
}
async function githubGraphql(query, variables) {
const response = await request('https://api.github.com/graphql', {
method: 'POST',
headers: { Authorization: `Bearer ${env.GITHUB_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
});
if (!response.ok) throw new Error(`GitHub GraphQL request failed (${response.status})`);
const payload = await response.json();
if (payload.errors) throw new Error('GitHub GraphQL returned errors');
return payload.data;
}
async function releaseFromGitHub() {
const [owner, repo] = env.GITHUB_REPOSITORY.split('/');
const tag = env.RELEASE_TAG || env.GITHUB_REF_NAME;
const response = await request(`https://api.github.com/repos/${owner}/${repo}/releases/tags/${encodeURIComponent(tag)}`, {
headers: { Authorization: `Bearer ${env.GITHUB_TOKEN}`, Accept: 'application/vnd.github+json' },
});
if (!response.ok) throw new Error(`release lookup failed (${response.status})`);
return response.json();
}
async function createOrFindReleaseDiscussion() {
const release = await releaseFromGitHub();
const [owner, name] = env.GITHUB_REPOSITORY.split('/');
const marker = releaseMarker(release.tag_name);
const data = await githubGraphql(View on GitHub (pinned to 8321021c54)