paperclipai/paperclip · error · ToolGatewayHttpError
connector_reauthorization_required
connector_reauthorization_required
Error message
Legacy authorization must be reconnected through Paperclip Cloud
What it means
Thrown by the tool gateway when a Gmail/Google connector grant was created under the legacy (unmanaged) OAuth client. Its refresh token cannot be exchanged through Paperclip Cloud, so the gateway marks the grant needs_reauthorization and refuses to refresh it, telling the caller to re-enroll the connector as a managed connector and reconnect.
Source
Thrown at server/src/services/tool-gateway.ts:2783
? Date.parse(grantOauth.accessTokenExpiresAt)
: Number.NaN;
const currentTime = options.now?.() ?? Date.now();
// The preferred GitHub App policy yields a non-expiring ghu_ token and no
// refresh token. Absence of an expiry is deliberate, not an invitation to
// enter the rotation path.
if (grantOauth?.accessTokenExpiresAt === null || grantOauth?.accessTokenExpiresAt === undefined) return grant;
const refreshedAt = typeof grantOauth.refreshedAt === "string" ? Date.parse(grantOauth.refreshedAt) : Number.NaN;
const rotationDue = !Number.isFinite(refreshedAt) || refreshedAt <= currentTime - 30 * 24 * 60 * 60_000;
if (!forceRefresh && Number.isFinite(expiresAt) && expiresAt > currentTime + 60 * 60_000 && !rotationDue) return grant;
if (oauth.strategy === "paperclip_id_connector") {
// Paperclip ID used different endpoints, signing metadata, envelope
// purposes, and a different Google client. Its refresh token cannot be
// exchanged through Paperclip Cloud. Let an unexpired access token finish
// its useful life, then require an explicit managed-connector enrollment
// and provider reconnect instead of sending it to the wrong client.
await db.update(connectionGrants).set({ status: "needs_reauthorization", updatedAt: new Date(currentTime) })
.where(eq(connectionGrants.id, grant.id));
throw new ToolGatewayHttpError(409, "Legacy authorization must be reconnected through Paperclip Cloud", "connector_reauthorization_required", {
connectionId: connection.id,
grantId: grant.id,
});
}
const existingFlight = gmailRefreshFlights.get(grant.id);
if (existingFlight) return existingFlight;
const refresh = (async () => {
const cloudConnector = currentCloudConnector();
if (!cloudConnector || !connectorSubject) {
await db.update(connectionGrants).set({ status: "needs_reauthorization", updatedAt: new Date(currentTime) })
.where(eq(connectionGrants.id, grant.id));
throw new ToolGatewayHttpError(409, "Managed authorization must be reconnected", "connector_reauthorization_required", {
connectionId: connection.id,
grantId: grant.id,
});
}
const accessRef = grant.credentialSecretRefs.find((ref) => ref.configPath === "oauth.access_token");
const refreshRef = grant.credentialSecretRefs.find((ref) => ref.configPath === "oauth.refresh_token");View on GitHub (pinned to 01ad858492)
Solutions
- Reconnect the connection through Paperclip Cloud (managed connector enrollment flow) to obtain a fresh refresh token for the current client
- Re-enroll the connector as a managed connector if it is still flagged as legacy
- Until reconnected, rely on the unexpired access token for short-lived operations
- Re-create the connection from scratch if reconnect is unavailable
Example fix
// before
const grant = await getGrant(connectionId); // legacy grant, refresh throws 409
await gateway.refresh(connectionId);
// after
if (grant.isLegacy) {
await reconnectViaPaperclipCloud(connectionId); // managed-connector enrollment
}
await gateway.refresh(connectionId); Defensive patterns
Strategy: try-catch
Validate before calling
const grant = await getGrant(connId); if (grant.status === "needs_reauthorization" || grant.isLegacy) await reconnectViaPaperclipCloud(connId);
Type guard
function isReauthRequired(e: unknown): e is ToolGatewayHttpError {
return e instanceof ToolGatewayHttpError && e.code === "connector_reauthorization_required";
} Try / catch
try { await gateway.callTool(session, connId, p); }
catch (e) {
if (e instanceof ToolGatewayHttpError && e.code === "connector_reauthorization_required") {
await reconnectManagedConnection(connId);
} else throw e;
} Prevention
- Migrate legacy OAuth grants to managed connectors proactively after enabling Paperclip Cloud
- Monitor grants with status needs_reauthorization and alert
- Track legacy client identifiers in grants and flag them before access tokens expire
- Re-connect connections after any Google client/app reconfiguration
When it happens
Trigger: Any managed token refresh attempt (refreshManagedGmailGrant path) on a grant whose credential refs carry the legacy client identity: the refresh token is from a different Google client than the current Paperclip Cloud connector, so exchanging it is rejected.
Common situations: An instance migrated from self-hosted/legacy Google OAuth setup to Paperclip Cloud managed connectors; a Google app re-configuration changed the client ID behind existing stored grants; grants created before managed-connector enrollment are still in use after their access token expired.
Related errors
- REAUTHORIZATION_REQUIRED
- connector_profile_invalid
- OAuth callback connection belongs to a missing company
- Discord bot is not installed in the selected server
- Managed GitHub connection is unavailable
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/489a9d360a934ce1.
Report an issue: GitHub.