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
- Ensure the user re-authenticates so the session is valid, then retry connecting — the error message's own remedy ('Sign in again').
- Gate the connect button on grantsQuery.isSuccess && !!grantsQuery.data?.currentUserId so the mutation can only run once the subject id exists.
- Inspect why the grants query returned no currentUserId (check network response, auth token, and company scoping) — fix the session/endpoint rather than just retrying.
- If the query is merely in-flight, use its isPending state to disable or defer the action instead of letting the mutation throw.
- 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
- Disable the connect action until grantsQuery.isSuccess and currentUserId is present.
- Handle 401 responses from the grants endpoint globally by routing the user to re-authentication.
- Show a loading state instead of leaving the connect button clickable while queries are in flight.
- On company switch, clear in-flight mutation state and re-check the subject before allowing consent flows.
- Write an activity/toast on auth-guard failures so users understand why the action did not proceed.
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
- device-login credential promotion rejected: ${result.outcome
- expired
- ${options.errorLabel} decision predicate failed: ${detail}
- ${options.errorLabel} decision predicate exited 0 (expected
- PAPERCLIP_BRIDGE_TOKEN is required.
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/4bac3f312a715bd1.
Report an issue: GitHub.