{"record":{"id":"7cf8c240e8f1b1f9","repo":"paperclipai/paperclip","slug":"request-failed","errorCode":"request_failed","errorMessage":"request_failed","messagePattern":"request_failed","errorType":"error_code","errorClass":"RequestFailure","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-discord-command-registration.ts","lineNumber":391,"sourceCode":"    try {\n      body = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n    } catch {\n      throw new RequestFailure(\"invalid_response\");\n    }\n    if (!response.ok) {\n      const value =\n        body && typeof body === \"object\"\n          ? (body as { retry_after?: unknown }).retry_after\n          : undefined;\n      const retry =\n        response.status === 429 &&\n        typeof value === \"number\" &&\n        Number.isFinite(value) &&\n        value >= 0 &&\n        value <= Number.MAX_SAFE_INTEGER\n          ? value\n          : undefined;\n      throw new RequestFailure(\"request_failed\", retry);\n    }\n    return body;\n  } catch (error) {\n    throw error instanceof RequestFailure\n      ? error\n      : new RequestFailure(\"request_failed\");\n  } finally {\n    void reader?.cancel().catch(() => undefined);\n  }\n}\n\n/**\n * Requires a current verified bot identity and caller-owned, app-scoped lease.\n * Discord POST is an UPSERT, not create-if-absent. The preflight GET prevents\n * known collisions but cannot fence a concurrent external administrator.\n * The public marker plus the independently persisted descriptor establishes\n * reconciliation identity; the marker alone never grants command ownership.\n * Unknown writes remain attempted until GET shows the exact expected command.","sourceCodeStart":373,"sourceCodeEnd":409,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-discord-command-registration.ts#L373-L409","documentation":"The Discord request helper's catch-all converts any non-RequestFailure error (network failure, abort, fetch rejection) into RequestFailure(\"request_failed\"), and non-ok responses (including 429 with a parsed retry_after) throw RequestFailure(\"request_failed\", retry). It signals the Discord API call itself failed, carrying an optional retry delay for rate limits.","triggerScenarios":"Discord returns a non-2xx status (401 bad bot token, 403 missing scopes/permissions, 404 wrong application/guild id, 429 rate limit); or fetch throws (DNS failure, TLS error, connection refused, abort signal fired).","commonSituations":"Expired or regenerated bot token after the bot was reset in the Developer Portal; bot not added to the guild (403) or missing applications.commands scope; exceeding Discord rate limits (429, retry_after honored); network outage on the host.","solutions":["For 429: wait for the retry_after seconds carried by the error and retry (the reconcile loop honors it).","For 401: regenerate/reset the bot token and update the stored credential.","For 403/404: confirm the bot is a guild member with applications.commands scope and that applicationId/guildId match the verified identity.","Check host networking (DNS, egress, TLS) if there is no HTTP status at all."],"exampleFix":"// before\nawait fetch(url, { headers: { authorization: `Bot ${staleToken}` } });\n// after: refresh token before calling, and honor retry\nif (failure.code === \"request_failed\" && failure.retryAfter)\n  await sleep(failure.retryAfter * 1000);","handlingStrategy":"retry","validationCode":"const res = await fetch(url, { headers: { authorization: `Bot ${token}` } });\nif (res.status === 401) throw new Error(\"Discord bot token invalid or expired\");\nif (res.status === 403 || res.status === 404) throw new Error(\"bot missing from guild or wrong application/guild id\");","typeGuard":null,"tryCatchPattern":"try { await reconcileDiscordCommandRegistration(input); }\ncatch (e) {\n  if (e instanceof RequestFailure && e.code === \"request_failed\" && typeof e.retryAfter === \"number\") {\n    await sleep(e.retryAfter * 1000); return retry();\n  }\n  throw e;\n}","preventionTips":["Rotate bot tokens through the Developer Portal and update stored credentials atomically.","Ensure the bot is invited with the applications.commands scope to the target guild.","Honor 429 retry_after; back off instead of hammering the API.","Verify applicationId/guildId match the bot's actual registration."],"tags":["network","http","discord","rate-limit"],"backgroundTag":"http-error-response","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}