{"record":{"id":"489a9d360a934ce1","repo":"paperclipai/paperclip","slug":"connector-reauthorization-required","errorCode":"connector_reauthorization_required","errorMessage":"Legacy authorization must be reconnected through Paperclip Cloud","messagePattern":"Legacy authorization must be reconnected through Paperclip Cloud","errorType":"http","errorClass":"ToolGatewayHttpError","httpStatus":409,"severity":"error","filePath":"server/src/services/tool-gateway.ts","lineNumber":3638,"sourceCode":"      Number.isFinite(expiresAt) &&\n      expiresAt > currentTime + 60 * 60_000 &&\n      !rotationDue\n    )\n      return grant;\n    if (oauth.strategy === \"paperclip_id_connector\") {\n      // Paperclip ID used different endpoints, signing metadata, envelope\n      // purposes, and a different Google client. Its refresh token cannot be\n      // exchanged through Paperclip Cloud. Let an unexpired access token finish\n      // its useful life, then require an explicit managed-connector enrollment\n      // and provider reconnect instead of sending it to the wrong client.\n      await db\n        .update(connectionGrants)\n        .set({\n          status: \"needs_reauthorization\",\n          updatedAt: new Date(currentTime),\n        })\n        .where(eq(connectionGrants.id, grant.id));\n      throw new ToolGatewayHttpError(\n        409,\n        \"Legacy authorization must be reconnected through Paperclip Cloud\",\n        \"connector_reauthorization_required\",\n        {\n          connectionId: connection.id,\n          grantId: grant.id,\n        },\n      );\n    }\n    const existingFlight = gmailRefreshFlights.get(grant.id);\n    if (existingFlight) return existingFlight;\n    const refresh = (async () => {\n      const cloudConnector = currentCloudConnector();\n      if (!cloudConnector || !connectorSubject) {\n        await db\n          .update(connectionGrants)\n          .set({\n            status: \"needs_reauthorization\",","sourceCodeStart":3620,"sourceCodeEnd":3656,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/tool-gateway.ts#L3620-L3656","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst grant = await getGrant(connectionId); // legacy grant, refresh throws 409\nawait gateway.refresh(connectionId);\n// after\nif (grant.isLegacy) {\n  await reconnectViaPaperclipCloud(connectionId); // managed-connector enrollment\n}\nawait gateway.refresh(connectionId);","handlingStrategy":"try-catch","validationCode":"const grant = await getGrant(connId);\nif (grant.status === \"needs_reauthorization\" || grant.isLegacy) await reconnectViaPaperclipCloud(connId);","typeGuard":"function isReauthRequired(e: unknown): e is ToolGatewayHttpError {\n  return e instanceof ToolGatewayHttpError && e.code === \"connector_reauthorization_required\";\n}","tryCatchPattern":"try { await gateway.callTool(session, connId, p); }\ncatch (e) {\n  if (e instanceof ToolGatewayHttpError && e.code === \"connector_reauthorization_required\") {\n    await reconnectManagedConnection(connId);\n  } else throw e;\n}","preventionTips":["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"],"tags":["oauth","connector","google","reauthorization"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}