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

  1. Reconnect the connection through Paperclip Cloud (managed connector enrollment flow) to obtain a fresh refresh token for the current client
  2. Re-enroll the connector as a managed connector if it is still flagged as legacy
  3. Until reconnected, rely on the unexpired access token for short-lived operations
  4. 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

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


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