Stirling-Tools/Stirling-PDF · error · Error

Failed to decline

Error message

Failed to decline

What it means

Thrown by decline() in useParticipantSession after workflowService.declineParticipation (or the following loadSession reload) rejects. Message extraction mirrors submitSignature: axios response.data.message or err.message, falling back to 'Failed to decline'. The error is set into state and re-thrown with the original as cause.

Source

Thrown at frontend/editor/src/proprietary/hooks/workflow/useParticipantSession.ts:98

  const decline = useCallback(
    async (token: string, reason?: string) => {
      setLoading(true);
      setError(null);
      try {
        const updatedParticipant = await workflowService.declineParticipation(
          token,
          reason,
        );
        setParticipant(updatedParticipant);
        // Reload session
        await loadSession(token);
      } catch (err: unknown) {
        const errorMsg = isAxiosError(err)
          ? err.response?.data?.message || err.message
          : (err instanceof Error ? err.message : undefined) ||
            "Failed to decline";
        setError(errorMsg);
        throw new Error(errorMsg, { cause: err });
      } finally {
        setLoading(false);
      }
    },
    [loadSession],
  );

  const downloadDocument = useCallback(
    async (token: string) => {
      setLoading(true);
      setError(null);
      try {
        const pdfBlob = await workflowService.getParticipantDocument(token);
        const url = window.URL.createObjectURL(pdfBlob);
        const a = document.createElement("a");
        a.href = url;
        a.download = session?.documentName || "document.pdf";
        document.body.appendChild(a);

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Read the axios message from error state to get the real backend reason.
  2. Confirm the token is still valid before offering decline; refresh the link if expired.
  3. Catch the re-thrown error at the UI and show the backend message, with a retry only for transient failures.
  4. Debounce/disable the decline button after first click to avoid races.

Example fix

// before
await decline(token, reason);

// after
try {
  await decline(token, reason);
} catch (e) {
  showDeclineError(e instanceof Error ? e.message : 'Decline failed');
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!token) {
  setError('Token is required to decline');
  return;
}

Type guard

export function isValidParticipantToken(token: string | undefined): token is string {
  return typeof token === 'string' && token.length > 0;
}

Try / catch

try {
  await decline(token, reason);
} catch (e) {
  showDeclineError(e instanceof Error ? e.message : 'Decline failed');
}

Prevention

When it happens

Trigger: Backend rejects the decline (token expired, session already completed, participant already declined/signed); network failure during the decline POST; the loadSession reload after a successful decline fails (e.g. session pruned).

Common situations: Participant clicks decline after the workflow deadline passed; admin cancelled the session; the token in the link has expired; double-click on decline races two requests; backend transient 5xx.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/45e742a822a85567. Report an issue: GitHub.