{"record":{"id":"49ce17625001ddfb","repo":"paperclipai/paperclip","slug":"oauth-refresh-outcome-unknown","errorCode":"oauth_refresh_outcome_unknown","errorMessage":"The previous OAuth refresh did not finish. Reconnect this app before retrying.","messagePattern":"The previous OAuth refresh did not finish\\. Reconnect this app before retrying\\.","errorType":"http","errorClass":"HttpError","httpStatus":422,"severity":"error","filePath":"server/src/services/tool-access.ts","lineNumber":4705,"sourceCode":"        .where(eq(toolConnections.id, connection.id))\n        .returning();\n      if (!updatedConnection) throw notFound(\"Tool connection not found\");\n\n      const remainingConnections = await tx\n        .select({ id: toolConnections.id })\n        .from(toolConnections)\n        .where(and(\n          eq(toolConnections.applicationId, updatedConnection.applicationId),\n          ne(toolConnections.status, \"archived\"),\n        ))\n        .limit(1);\n\n      let applicationArchived = false;\n      if (remainingConnections.length === 0) {\n        const [application] = await tx\n          .update(toolApplications)\n          .set({ status: \"archived\", archivedAt: now, updatedAt: now })\n          .where(and(\n            eq(toolApplications.id, updatedConnection.applicationId),\n            ne(toolApplications.status, \"archived\"),\n          ))\n          .returning({ id: toolApplications.id });\n        applicationArchived = Boolean(application);\n      }\n\n      return { connection: updatedConnection, applicationArchived };\n    });\n\n    // Only now, with every access path closed, revoke the credentials. Each\n    // `secrets.remove` marks the row deleted before it calls the provider, so a\n    // provider error leaves an unresolvable secret and a resumable removal\n    // rather than a half-open app.\n    const candidateSecretIds = [\n      ...connection.credentialRefs.map((ref) => ref.secretId),\n      ...connection.credentialSecretRefs.map((ref) => ref.secretId),\n      ...grantRows.flatMap((grant) => (grant.credentialSecretRefs ?? []).map((ref) => ref.secretId)),","sourceCodeStart":4687,"sourceCodeEnd":4723,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/tool-access.ts#L4687-L4723","documentation":"Refresh-lease contention guard: the conditional UPDATE claiming the connection's refresh lease (config refreshLease id null) matched zero rows because another process is mid-refresh. Prevents concurrent token refreshes from clobbering each other; the concurrent refresh is at fault.","triggerScenarios":"Thrown at server/src/services/tool-access.ts:4547 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["A previous refresh attempt is incomplete/locked. Reconnect the app to reset the OAuth state before retrying."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}