different-ai/openwork · error

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

Error message

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

What it means

Thrown by useRevokePluginAccess when DELETE /v1/plugins/:id/access/:grantId returns neither 204 nor ok. The message embeds the HTTP status unless the payload supplies a server error message. 403 'reauth' payloads surface as ReauthRequiredError instead.

Source

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

      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(
          `/v1/plugins/${encodeURIComponent(input.pluginId)}/access/${encodeURIComponent(input.grantId)}`,
          { method: "DELETE" },
          15000,
        );
        if (response.status !== 204 && !response.ok) {
          throw getRequestError(payload, response, `Failed to revoke plugin access (${response.status}).`);
        }
      });
      return input.pluginId;
    },
    onSuccess: (pluginId) => {
      queryClient.invalidateQueries({ queryKey: pluginAccessQueryKeys.detail(pluginId) });
    },
  });
}

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. On 404, refetch pluginAccessQueryKeys.detail(pluginId) and treat the grant as already revoked.
  2. On 401/403, re-authenticate or verify access-management rights.
  3. On 5xx, retry after confirming current grant state to avoid acting on stale data.
  4. Validate grantId came from the latest access list response before calling revoke.

Example fix

// before
if (response.status !== 204 && !response.ok) {
  throw getRequestError(payload, response, `Failed to revoke plugin access (${response.status}).`);
}
// after
if (response.status === 404) return input.pluginId; // already revoked
if (response.status !== 204 && !response.ok) {
  throw getRequestError(payload, response, `Failed to revoke plugin access (${response.status}).`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!input.pluginId || !input.grantId || typeof input.grantId !== "string") {
  throw new Error("A valid pluginId and grantId are required to revoke access.");
}

Type guard

function isGrantId(value: unknown): value is string {
  return typeof value === "string" && value.length > 0;
}

Try / catch

try {
  await revokePluginAccess.mutateAsync({ pluginId, grantId });
} catch (error) {
  if (isReauthRequiredError(error)) return startReauth();
  if (error.message.includes("(404)")) return; // already revoked
  toast.error(error.message);
} finally {
  await queryClient.invalidateQueries({ queryKey: pluginAccessQueryKeys.detail(pluginId) });
}

Prevention

When it happens

Trigger: Revoking an access grant that no longer exists (404 — concurrent revocation or stale list), lacking permission to manage plugin access (403), expired session (401), malformed grantId, or server error.

Common situations: Stale access list where the grant was already removed; admin permissions changed mid-session; plugin itself deleted while its access list was still open.

Related errors


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