{"record":{"id":"77b9e092ec9358aa","repo":"affaan-m/ECC","slug":"discussion-receipt-lookup-failed","errorCode":null,"errorMessage":"discussion receipt lookup failed","messagePattern":"discussion receipt lookup failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/discord/release-announce.mjs","lineNumber":113,"sourceCode":"  if (!/^\\d+$/.test(env.DISCUSSION_NUMBER || '')) throw new Error('discussion number is invalid');\n  const response = await request(`https://api.github.com/repos/${env.GITHUB_REPOSITORY}/discussions/${env.DISCUSSION_NUMBER}`, {\n    headers: { Authorization: `Bearer ${env.GITHUB_TOKEN}`, Accept: 'application/vnd.github+json' },\n  });\n  if (!response.ok) throw new Error(`discussion lookup failed (${response.status})`);\n  const discussion = await response.json();\n  if (discussion.category?.name !== 'Announcements') throw new Error('discussion is not an Announcement');\n  return { id: discussion.node_id, title: discussion.title, body: discussion.body, url: discussion.html_url };\n}\n\nasync function findReceiptComment(discussionId, marker) {\n  let cursor = null;\n  for (let page = 0; page < 50; page += 1) {\n    const data = await githubGraphql(\n      `query($id:ID!,$after:String){node(id:$id){... on Discussion{comments(first:100,after:$after){nodes{id body author{login}} pageInfo{hasNextPage endCursor}}}}}`,\n      { id: discussionId, after: cursor },\n    );\n    const comments = data.node?.comments;\n    if (!comments) throw new Error('discussion receipt lookup failed');\n    const receipt = findDiscussionReceipt(comments.nodes, marker);\n    if (receipt) return receipt;\n    if (!comments.pageInfo.hasNextPage) return null;\n    cursor = comments.pageInfo.endCursor;\n  }\n  throw new Error('discussion receipt lookup exceeded page budget');\n}\n\nasync function addReceiptComment(discussionId, body) {\n  const data = await githubGraphql(\n    `mutation($id:ID!,$body:String!){addDiscussionComment(input:{discussionId:$id,body:$body}){comment{id}}}`,\n    { id: discussionId, body },\n  );\n  return data.addDiscussionComment.comment.id;\n}\n\nasync function deleteReceiptComment(commentId) {\n  await githubGraphql(","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/discord/release-announce.mjs#L95-L131","documentation":"findReceiptComment pages through a discussion's comments via GraphQL node(id) and throws this when `data.node?.comments` is missing. That means the node lookup returned null/undefined or lacked comments — typically because the discussionId is invalid, the token lacks read:discussion scope, or the node is not a Discussion the caller can see.","triggerScenarios":"githubGraphql succeeded at the HTTP level but data.node is null (invalid node ID, insufficient permission) or node exists but `comments` field errored/was omitted, so the optional chain yields undefined.","commonSituations":"A discussion ID from another repository or a stale ID from deleted discussions; token missing the read:discussion scope so node(id) returns null; GitHub returning partial results with in-band errors silently swallowed by the payload.errors check upstream being bypassed is impossible — but a malformed alias/query would produce missing fields.","solutions":["Verify the discussionId (node_id) is correct with a direct query: node(id:...) { ... on Discussion { id number } }","Confirm the token has read:discussion scope for the repository","Log the raw GraphQL response (data and errors) to see whether node is null vs comments missing","Fall back to the REST API GET /repos/{owner}/{repo}/discussions/{number}/comments to cross-check"],"exampleFix":"// before\nconst comments = data.node?.comments;\nif (!comments) throw new Error('discussion receipt lookup failed');\n// after\nif (!data.node) throw new Error(`discussion node ${discussionId} not found or not readable`);\nconst comments = data.node.comments;\nif (!comments) throw new Error(`discussion ${discussionId} returned no comments field: ${JSON.stringify(data)}`);","handlingStrategy":"try-catch","validationCode":"// verify the node resolves before paging comments\nconst probe = await githubGraphql('query($id:ID!){node(id:$id){id ... on Discussion{number}}}', { id: discussionId });\nif (!probe.node) throw new Error(`Discussion node ${discussionId} not readable — check id and token scopes`);","typeGuard":"function isDiscussionComments(node) {\n  return node != null && typeof node === 'object'\n    && node.comments != null\n    && Array.isArray(node.comments.nodes)\n    && typeof node.comments.pageInfo?.hasNextPage === 'boolean';\n}","tryCatchPattern":"try {\n  const receipt = await findReceiptComment(discussionId, marker);\n} catch (err) {\n  if (err.message === 'discussion receipt lookup failed') {\n    console.error(`Node ${discussionId} unreadable: check node_id validity and read:discussion scope`);\n  }\n  throw err;\n}","preventionTips":["Persist the discussion node_id from the create mutation and reuse it unchanged","Grant read:discussion scope to the token","Include GraphQL error details in githubGraphql's thrown error to expose in-band failures","Cross-check with the REST endpoint /discussions/{number}/comments when in doubt"],"tags":["graphql","github-api","discussions"],"backgroundTag":"unexpected-api-response-shape","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"}