different-ai/openwork · error

Failed to grant plugin access (${response.status}).

Error message

Failed to grant plugin access (${response.status}).

What it means

Thrown by useGrantPluginAccess when POST /v1/plugins/:id/access returns non-ok. getRequestError prefers the server's error message; otherwise the fallback with the HTTP status is used. A 403 'reauth' payload becomes ReauthRequiredError.

Source

Thrown at ee/apps/den-web/app/(den)/dashboard/_components/plugin-access-data.tsx:118

type GrantPluginAccessBody =
  | { orgMembershipId: string; teamId?: never; orgWide?: never; role: PluginAccessRole }
  | { orgMembershipId?: never; teamId: string; orgWide?: never; role: PluginAccessRole }
  | { orgMembershipId?: never; teamId?: never; orgWide: true; role: PluginAccessRole };

export function useGrantPluginAccess() {
  const queryClient = useQueryClient();
  const { runReauthableAction } = useOrgDashboard();

  return useMutation({
    mutationFn: async (input: { pluginId: string; body: GrantPluginAccessBody }) => {
      await runReauthableAction("grant-plugin-access", async () => {
        const { response, payload } = await requestJson(
          `/v1/plugins/${encodeURIComponent(input.pluginId)}/access`,
          { method: "POST", body: JSON.stringify(input.body) },
          15000,
        );
        if (!response.ok) {
          throw getRequestError(payload, response, `Failed to grant plugin access (${response.status}).`);
        }
      });
      return input.pluginId;
    },
    onSuccess: (pluginId) => {
      queryClient.invalidateQueries({ queryKey: pluginAccessQueryKeys.detail(pluginId) });
    },
  });
}

export function useRevokePluginAccess() {
  const queryClient = useQueryClient();
  const { runReauthableAction } = useOrgDashboard();

  return useMutation({
    mutationFn: async (input: { pluginId: string; grantId: string }) => {
      await runReauthableAction("revoke-plugin-access", async () => {
        const { response, payload } = await requestJson(

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Check the status: 400 → validate the access body (subject and role) before sending; 404 → refresh pluginQueryKeys.all, the plugin was removed.
  2. On 401/403, re-authenticate or confirm sharing permissions; handle ReauthRequiredError explicitly.
  3. Invalidate pluginAccessQueryKeys.detail(pluginId) and refetch before retrying to avoid duplicates.
  4. Resolve any payment_required gate reported in the server message.

Example fix

// before
throw getRequestError(payload, response, `Failed to grant plugin access (${response.status}).`);
// after
if (isReauthRequiredError(await Promise.resolve())) return; // pattern: catch and branch
const err = getRequestError(payload, response, `Failed to grant plugin access (${response.status}).`);
if (isReauthRequiredError(err)) return startReauth();
throw err;
Defensive patterns

Strategy: validation

Validate before calling

if (!input.pluginId || typeof input.pluginId !== "string") throw new Error("pluginId is required.");
if (!isValidAccessBody(input.body)) throw new Error("Select a valid subject and role before granting plugin access.");

Type guard

function isValidAccessBody(body: unknown): body is { subjectId: string; role: string } {
  return typeof body === "object" && body !== null
    && typeof (body as { subjectId?: unknown }).subjectId === "string"
    && typeof (body as { role?: unknown }).role === "string";
}

Try / catch

try {
  await grantPluginAccess.mutateAsync({ pluginId, body });
} catch (error) {
  if (isReauthRequiredError(error)) return startReauth();
  toast.error(error.message);
  await queryClient.invalidateQueries({ queryKey: pluginAccessQueryKeys.detail(pluginId) });
}

Prevention

When it happens

Trigger: Granting plugin access with an invalid body (unknown subject ID or role — 400), plugin not found (404), insufficient permission to share the plugin (403), expired token (401), or org plugin-access limits/payment_required.

Common situations: Sharing a plugin with a member removed from the org, an unpublished or deleted plugin still cached in the UI, or role enum drift between client and API versions.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/c5e433a16ced9b60. Report an issue: GitHub.