{"record":{"id":"a5e6248599159952","repo":"different-ai/openwork","slug":"only-workspace-admins-can-cancel-invitations","errorCode":null,"errorMessage":"Only workspace admins can cancel invitations.","messagePattern":"Only workspace admins can cancel invitations\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_providers/org-dashboard-provider.tsx","lineNumber":657,"sourceCode":"        }\n\n        const url = payload && typeof payload === \"object\" && \"url\" in payload && typeof payload.url === \"string\"\n          ? payload.url\n          : null;\n        if (!url) {\n          throw new Error(\"Seat billing checkout response did not include a URL.\");\n        }\n\n        window.location.href = url;\n      });\n    } finally {\n      setMutationBusy(null);\n    }\n  }\n\n  async function cancelInvitation(invitationId: string) {\n    if (!getCurrentAccess().canCancelInvitations) {\n      throw new Error(\"Only workspace admins can cancel invitations.\");\n    }\n\n    await runMutation(\"cancel-invitation\", async () => {\n      ensureActiveOrganizationSelected();\n      const { response, payload } = await requestJson(\n        `/v1/invitations/${encodeURIComponent(invitationId)}/cancel`,\n        { method: \"POST\", body: JSON.stringify({}) },\n        12000,\n      );\n\n      if (!response.ok) {\n        throw getRequestError(payload, response, `Failed to cancel invitation (${response.status}).`);\n      }\n    });\n  }\n\n  async function updateMemberRole(memberId: string, role: string) {\n    if (!getCurrentAccess().canManageRoles) {","sourceCodeStart":639,"sourceCodeEnd":675,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_providers/org-dashboard-provider.tsx#L639-L675","documentation":"cancelInvitation requires access.canCancelInvitations before it will POST /v1/invitations/{id}/cancel. If the current member is not a workspace admin (per getOrgAccessFlags), the provider throws \"Only workspace admins can cancel invitations.\" and no network request is made. It is the client-side authorization gate for revoking pending org invitations.","triggerScenarios":"Calling cancelInvitation(invitationId) (e.g. from ManageMembersScreen's pending-invitations list) while the signed-in member's resolved role lacks canCancelInvitations.","commonSituations":"A non-admin member opens the members screen and clicks Revoke on a pending invite; role was downgraded but the UI still shows cancel buttons; stale orgContext after switching organizations.","solutions":["Have a workspace admin cancel the invitation.","Check your role in the active organization before managing invitations.","Refresh org context to pick up recent role changes and retry if you were promoted.","Render cancel controls only when access.canCancelInvitations is true."],"exampleFix":"// before\n{invitations.map((inv) => (\n  <Button onClick={() => cancelInvitation(inv.id)}>Revoke</Button>\n))}\n\n// after\n{access.canCancelInvitations && invitations.map((inv) => (\n  <Button onClick={() => cancelInvitation(inv.id)}>Revoke</Button>\n))}","handlingStrategy":"try-catch","validationCode":"if (!access.canCancelInvitations) return; // don't render or call cancelInvitation","typeGuard":null,"tryCatchPattern":"try {\n  await cancelInvitation(invitationId);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"Only workspace admins\")) {\n    toast(e.message);\n  } else throw e;\n}","preventionTips":["Hide revoke buttons unless access.canCancelInvitations.","Refetch org context after any role/invite changes.","Confirm the active organization before member management."],"tags":["authorization","rbac","invitations"],"backgroundTag":"insufficient-permissions","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}