paperclipai/paperclip · error

Sign in again to connect your own account.

Error message

Sign in again to connect your own account.

What it means

This client-side guard in AppDetail.tsx throws when the user attempts to start a personal OAuth authorization for an Apps catalog connection before the grants query has resolved a currentUserId. The server requires the subject of a personal connection to be the calling user, so without a known user id there is no valid consent request to start. The message tells the user their session identity is missing and they should sign in again.

Source

Thrown at ui/src/pages/apps/AppDetail.tsx:327

        body: error instanceof Error ? error.message : "Please try again.",
        tone: "error",
      }),
  });

  const invalidateGrants = () => {
    queryClient.invalidateQueries({ queryKey: queryKeys.tools.connectionGrants(connectionId) });
    queryClient.invalidateQueries({ queryKey: queryKeys.tools.connection(connectionId) });
  };

  /**
   * "Connect as me" and "Reconnect" for the signed-in user's own identity. The
   * subject is always the caller — the server refuses any other subject — so
   * there is no path here to start consent on a coworker's behalf.
   */
  const startPersonalAuth = useMutation({
    mutationFn: () => {
      const subjectUserId = grantsQuery.data?.currentUserId;
      if (!subjectUserId) throw new Error("Sign in again to connect your own account.");
      return toolsApi.startPersonalAuthorization(selectedCompanyId!, connectionId, {
        subjectUserId,
        returnTo: appTabHref(connectionId, "permissions"),
      });
    },
    onSuccess: async ({ url, handoff }) => {
      try {
        const target = await prepareOAuthNavigation({ authorizationUrl: url, handoff });
        if (target.kind === "reauthentication" && handoff) {
          savePendingCloudHandoff(handoff.session);
        }
        navigateTopLevel(target.url);
      } catch (error) {
        pushToast({
          title: "Couldn't start sign-in",
          body: error instanceof Error ? error.message : "Please try again.",
          tone: "error",
        });

View on GitHub (pinned to 01ad858492)

Solutions

  1. Ensure the user re-authenticates so the session is valid, then retry connecting — the error message's own remedy ('Sign in again').
  2. Gate the connect button on grantsQuery.isSuccess && !!grantsQuery.data?.currentUserId so the mutation can only run once the subject id exists.
  3. Inspect why the grants query returned no currentUserId (check network response, auth token, and company scoping) — fix the session/endpoint rather than just retrying.
  4. If the query is merely in-flight, use its isPending state to disable or defer the action instead of letting the mutation throw.
  5. Add an onError handler on the mutation to surface a friendly re-sign-in prompt/toast instead of an unhandled throw.

Example fix

// before
const startPersonalAuth = useMutation({
  mutationFn: () => {
    const subjectUserId = grantsQuery.data?.currentUserId;
    if (!subjectUserId) throw new Error("Sign in again to connect your own account.");
    return toolsApi.startPersonalAuthorization(selectedCompanyId!, connectionId, {
      subjectUserId,
      returnTo: appTabHref(connectionId, "permissions"),
    });
  },
// after
const subjectUserId = grantsQuery.data?.currentUserId;
const canStartAuth = grantsQuery.isSuccess && !!subjectUserId;

const startPersonalAuth = useMutation({
  mutationFn: () => {
    return toolsApi.startPersonalAuthorization(selectedCompanyId!, connectionId, {
      subjectUserId: subjectUserId!,
      returnTo: appTabHref(connectionId, "permissions"),
    });
  },
  onError: () => showToast("Your session expired — sign in again to connect your account."),
// (button: disabled={!canStartAuth})
Defensive patterns

Strategy: validation

Validate before calling

if (grantsQuery.isPending) return; // wait for grants to load
const subjectUserId = grantsQuery.data?.currentUserId;
if (!subjectUserId) {
  redirectToSignIn({ returnTo: appTabHref(connectionId, "permissions") });
  return;
}
startPersonalAuth.mutate();

Type guard

function hasSubjectUser(
  g: { currentUserId?: string | null } | undefined
): g is { currentUserId: string } {
  return typeof g?.currentUserId === "string" && g.currentUserId.length > 0;
}
// usage: if (!hasSubjectUser(grantsQuery.data)) redirect to sign-in;

Try / catch

try {
  const { url, handoff } = await startPersonalAuth.mutateAsync();
  window.location.href = handoff ? url : url; // proceed with handoff
} catch (e) {
  if (e instanceof Error && e.message.includes("Sign in again")) {
    showToast("Your session expired. Please sign in and try connecting again.");
  } else {
    showToast("Could not start authorization. Please try again.");
  }
}

Prevention

When it happens

Trigger: Clicking the 'connect your own account' action while grantsQuery.data is undefined (query still loading) or its currentUserId field is absent/null — typically when the session expired, the grants endpoint returned an empty/error payload, or the mutation fires before the grants query finishes fetching.

Common situations: A stale or expired session token where the /grants endpoint no longer returns the current user; a slow network where the user clicks connect before grantsQuery resolves; an API error (401/403) swallowed into an undefined query result; switching companies where grants are refetched and momentarily empty.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/4bac3f312a715bd1. Report an issue: GitHub.