Stirling-Tools/Stirling-PDF · error · Error

errorData.error || Failed to synchronize user upgrade

Error message

errorData.error || Failed to synchronize user upgrade

What it means

Thrown by synchronizeUserUpgrade() when the POST /api/v1/user-role/promptToAuthUser backend endpoint returns a non-OK HTTP status. The function attempts to parse errorData.error from the JSON body, falling back to a generic message if parsing fails. This endpoint upgrades an anonymous user to an authenticated user in the backend's security context — the backend derives the user from the session cookie, not the request body.

Solutions

  1. Read errorData.error for the backend's specific error message
  2. Verify the session cookie is still valid — re-authenticate via Supabase and retry
  3. Check backend logs for /user-role/promptToAuthUser exceptions
  4. Ensure credentials:'include' is actually sending the cookie (check CORS Allow-Credentials header on the backend)
  5. Retry the sync after confirming the backend is healthy

Example fix

// before
if (!response.ok) {
  const errorData = await response.json().catch(() => ({ error: "Failed to synchronize user upgrade" }));
  throw new Error(errorData.error || "Failed to synchronize user upgrade");
}

// after
if (!response.ok) {
  const errorData = await response.json().catch(() => ({ error: "Failed to synchronize user upgrade" }));
  if (response.status === 401 || response.status === 403) {
    // Session expired — re-auth and retry once
    await refreshSession();
    return synchronizeUserUpgrade(authMethod);
  }
  throw new Error(errorData.error || "Failed to synchronize user upgrade");
}
Defensive patterns

Strategy: retry

Validate before calling

// Verify session is valid before syncing
const { data: { session } } = await supabase.auth.getSession();
if (!session?.access_token) {
  await refreshSession();
  return;
}

Try / catch

try {
  await synchronizeUserUpgrade(authMethod);
} catch (e) {
  if (e instanceof Error && (e.message.includes('401') || e.message.includes('403'))) {
    // Session expired — re-authenticate and retry once
    await refreshSession();
    await synchronizeUserUpgrade(authMethod);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: The backend session cookie has expired between the Supabase auth upgrade and this sync call; the backend security context cannot resolve the user from the cookie; the backend throws during the upgrade (e.g., user already upgraded, database error); the backend is down or returns 5xx.

Common situations: Session cookie expired (credentials:'include' sends cookies but they may be stale); backend restarted and lost session state; user's anonymous session in the backend doesn't match the newly authenticated Supabase identity; CORS preflight failure blocking the POST.

Related errors


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

Appendix: source

Thrown at frontend/editor/src/saas/services/userService.ts:41

  const formData = new URLSearchParams();
  if (authMethod) {
    formData.append("authMethod", authMethod);
  }

  const response = await fetch(`${API_BASE}/user-role/promptToAuthUser`, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    credentials: "include", // Include cookies for authentication
    body: formData.toString(),
  });

  if (!response.ok) {
    const errorData = await response
      .json()
      .catch(() => ({ error: "Failed to synchronize user upgrade" }));
    throw new Error(errorData.error || "Failed to synchronize user upgrade");
  }

  return response.json();
};

View on GitHub (pinned to 9ef20dcab8)