{"record":{"id":"2ae4399d4f6247fb","repo":"paperclipai/paperclip","slug":"discord-command-registration-authorization-denied","errorCode":null,"errorMessage":"Discord command registration authorization denied","messagePattern":"Discord command registration authorization denied","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-discord-command-registration.ts","lineNumber":441,"sourceCode":"    input.botToken.length > 4096 ||\n    /[\\r\\n]/.test(input.botToken)\n  ) {\n    throw new Error(\"Invalid Discord command registration authority\");\n  }\n  // The caller may retain its options while an authorization hook is held.\n  // Snapshot the validated identity, credential and HTTP function before await.\n  input = {\n    ...input,\n    scope: state.scope,\n    state,\n    runtimeFence: Object.freeze(runtimeFence.data),\n    verifiedIdentity: Object.freeze({ ...input.verifiedIdentity }),\n  };\n  const authorize = async (stage: DiscordCommandRegistrationStage) => {\n    try {\n      await input.authorize(stage);\n    } catch {\n      throw new Error(\"Discord command registration authorization denied\");\n    }\n  };\n  const persist = async (next: DiscordCommandRegistration) => {\n    freezeState(next);\n    try {\n      await input.commit(state!, next);\n    } catch {\n      throw new Error(\"Discord command registration persistence unproven\");\n    }\n    state = next;\n  };\n  const settle = async (\n    command: RemoteCommand,\n  ): Promise<DiscordCommandRegistrationResult> => {\n    const next: Extract<DiscordCommandRegistration, { phase: \"registered\" }> = {\n      schema: state!.schema,\n      scope: state!.scope,\n      ownerId: state!.ownerId,","sourceCodeStart":423,"sourceCodeEnd":459,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-discord-command-registration.ts#L423-L459","documentation":"Each stage of command reconciliation calls the caller-supplied authorize(stage) hook; any throw from that hook is converted to 'Discord command registration authorization denied'. The library deliberately discards the original error to avoid leaking details, so callers see only that authorization was refused at some stage.","triggerScenarios":"The injected authorize callback rejects a stage (e.g. 'preflight', 'settle') because the actor lacks permission, the company lease expired, or the hook enforces a policy that fails mid-reconciliation after a state change.","commonSituations":"Approval-gate/authorization service downtime causing the hook to throw; revoking an actor's permission between authorize stages; a bug in the app's authorize callback (e.g. DB query failure inside it being misread as denial).","solutions":["Inspect the authorize callback's own logs — the original error is swallowed here, so diagnose at the hook.","Re-run reconciliation after restoring the authorization service / actor permissions.","Ensure the authorize hook only throws for genuine denials; handle transient internal errors inside it.","Verify the actor still holds the required role/lease for the whole reconcile window."],"exampleFix":"// before (inside app's authorize hook)\nconst row = await db.query(...); // throws on transient DB error -> seen as denial\n// after\ntry { const row = await db.query(...); }\ncatch (e) { if (isTransient(e)) { retry(); return; } throw new AuthorizationDenied(); }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await reconcileDiscordCommandRegistration(input); }\ncatch (e) {\n  if ((e as Error).message === \"Discord command registration authorization denied\") {\n    log.warn(\"authorize stage rejected\", { actor: input.actorId, stageHint: \"see authorize hook logs\" });\n    return { status: \"authorization_denied\" };\n  }\n  throw e;\n}","preventionTips":["Make the authorize hook log its own denial reasons (the wrapper swallows them).","Distinguish transient internal errors inside the hook from genuine denials.","Verify actor permissions/leases cover the full reconcile window before starting."],"tags":["authorization","discord","permissions"],"backgroundTag":"permission-denied","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"}