{"record":{"id":"c7820019b6422ac7","repo":"affaan-m/ECC","slug":"announcement-has-a-pending-receipt-inspect-discor","errorCode":null,"errorMessage":"announcement has a pending receipt; inspect Discord before clearing it","messagePattern":"announcement has a pending receipt; inspect Discord before clearing it","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/discord/release-announce.mjs","lineNumber":159,"sourceCode":"    body: body ? JSON.stringify(body) : undefined,\n  });\n  if (!response.ok) throw new Error(`Discord request failed (${response.status})`);\n  return response.status === 204 ? null : response.json();\n}\n\nasync function deliver(discussion) {\n  const key = announcementKey({ repository: env.GITHUB_REPOSITORY, discussionId: discussion.id });\n  if (env.DISCORD_ANNOUNCE_WEBHOOK_URL) {\n    if (!env.GITHUB_TOKEN) throw new Error('GitHub receipt configuration is missing');\n    const webhookUrl = normalizeDiscordWebhookUrl(env.DISCORD_ANNOUNCE_WEBHOOK_URL);\n    const marker = discussionReceiptMarker(key);\n    const existingReceipt = await findReceiptComment(discussion.id, marker);\n    if (existingReceipt) {\n      if (discussionReceiptStatus(existingReceipt) === 'complete') {\n        console.log('announcement already delivered');\n        return;\n      }\n      throw new Error('announcement has a pending receipt; inspect Discord before clearing it');\n    }\n    const claimId = await addReceiptComment(discussion.id, `${marker}\\n\\nDiscord delivery: pending.`);\n    const payload = buildDiscordPayload({ title: discussion.title, body: discussion.body, url: discussion.url, key });\n    delete payload.nonce;\n    delete payload.enforce_nonce;\n    const response = await request(webhookUrl, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(payload),\n    });\n    if (!response.ok) {\n      await deleteReceiptComment(claimId);\n      throw new Error(`Discord webhook request failed (${response.status})`);\n    }\n    const message = await response.json();\n    await addReceiptComment(discussion.id, `${marker}\\n\\nDiscord delivery: complete (message ${message.id}).`);\n    await deleteReceiptComment(claimId).catch(() => {\n      console.warn('announcement delivered; pending receipt cleanup requires attention');","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/discord/release-announce.mjs#L141-L177","documentation":"Before sending to Discord, the script posts a 'pending' receipt claim comment on the GitHub discussion to make delivery idempotent. If an existing receipt is found but is not marked complete, the script refuses to act: a previous run may have sent the message but never finished, so re-running could duplicate the announcement. It throws this error to force human inspection.","triggerScenarios":"findReceiptComment(discussion.id, marker) returns a receipt whose discussionReceiptStatus is not 'complete' — i.e. a leftover 'pending' receipt from a crashed or interrupted run.","commonSituations":"A previous run posted to Discord but crashed before writing the 'complete' receipt; workflow was cancelled mid-delivery; Discord webhook succeeded but the GitHub comment API call failed, leaving the pending claim.","solutions":["Check Discord for whether the announcement was actually posted","If posted and correct, edit or delete the pending receipt comment on the GitHub discussion (or update its status to complete), then re-run","If not posted, delete the pending receipt comment and re-run the workflow"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await deliver(discussion); } catch (e) { if (/pending receipt/.test(e.message)) { console.error('Inspect the GitHub discussion receipt comment and Discord channel manually, then clear the pending receipt'); process.exit(2); } throw e; }","preventionTips":["Check the GitHub discussion for a pending receipt comment before re-running the workflow","After crashes, manually reconcile Discord state before retrying","Keep delivery runs short-lived to reduce crash windows; avoid cancelling mid-run"],"tags":["github","idempotency","state-conflict"],"backgroundTag":"invalid-state-transition","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"}