{"record":{"id":"f2008e1a73c0b01a","repo":"different-ai/openwork","slug":"mcp-authorization-callback-failed","errorCode":"MCP_AUTHORIZATION_CALLBACK_FAILED","errorMessage":"Enterprise MCP failed during ${phaseLabel[input.operationPhase]}${request}.","messagePattern":"Enterprise MCP failed during (.+?)(.+?)\\.","errorType":"exception","errorClass":"EnterpriseMcpClientError","httpStatus":null,"severity":"critical","filePath":"packages/enterprise-mcp-client/src/enterprise-mcp-client.ts","lineNumber":572,"sourceCode":"            operationFailed = true\n            let credentialCleanupError: unknown = null\n            if (exchangedTokens && credentialPort) {\n              try {\n                const cleanupController = new AbortController()\n                await credentialPort.invalidate({\n                  context: {\n                    connectionId: input.connection.id,\n                    commitExpiresAt: clock.now() + closeTimeoutMs,\n                    signal: cleanupController.signal,\n                  },\n                  reason: \"post-authorization-validation-failed\",\n                })\n              } catch (cleanupError) {\n                credentialCleanupError = cleanupError\n              }\n            }\n            if (credentialCleanupError) {\n              throw new EnterpriseMcpClientError({\n                operationPhase: \"authorization-callback\",\n                requestPhase: session.observer.lastRequestPhase(),\n                cause: new AggregateError(\n                  [error, credentialCleanupError],\n                  \"Post-authorization validation failed and the exchanged credentials could not be invalidated.\",\n                ),\n              })\n            }\n            throw error\n          } finally {\n            try {\n              await closeWithinDeadline(() => session.client.close(), closeTimeoutMs)\n            } catch (error) {\n              if (!operationFailed) {\n                throw new EnterpriseMcpClientError({\n                  operationPhase: \"shutdown\",\n                  requestPhase: session.observer.lastRequestPhase(),\n                  cause: error,","sourceCodeStart":554,"sourceCodeEnd":590,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/enterprise-mcp-client/src/enterprise-mcp-client.ts#L554-L590","documentation":"In completeAuthorization(), the authorization code was exchanged (tokens saved) but the subsequent validation (protocol handshake / tools/list) failed, AND the cleanup attempt to invalidate the just-exchanged credentials via credentialPort.invalidate() also failed. The client wraps both failures in an AggregateError inside an EnterpriseMcpClientError with operationPhase \"authorization-callback\" and code MCP_AUTHORIZATION_CALLBACK_FAILED. This is serious: possibly-orphaned OAuth credentials remain persisted and unvalidated.","triggerScenarios":"finishAuth(code) succeeds, then connectWithProtocolNegotiation or listTools throws (e.g. server 401/5xx/network drop), then persistence.credentials.invalidate({ reason: \"post-authorization-validation-failed\" }) also throws (persistence backend down, commit deadline expired, signal aborted).","commonSituations":"Persistence adapter (database) outage coinciding with a failing MCP server; lifecycle deadline expiring mid-callback; the OAuth adapter rejecting the invalidation because the transaction already committed or the signal was aborted.","solutions":["Inspect the AggregateError's two causes: the primary validation failure and the cleanup failure; fix each accordingly.","Manually invalidate the exchanged credentials for this connectionId in your persistence store to avoid orphaned tokens.","Check the persistence adapter's health and its commit deadline handling; increase the lifecycle window if it expired mid-callback.","Retry completeAuthorization after cleanup is possible; the exchange may need to be redone from a fresh authorization code."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"async function assertPersistenceHealthy(credentialsPort) {\n  // cheap probe that the credential store accepts writes before exchanging the code\n  await credentialsPort.ping?.();\n}","typeGuard":"function isAggregateCleanupError(e: unknown): e is AggregateError {\n  return e instanceof AggregateError && e.message.includes(\"could not be invalidated\");\n}","tryCatchPattern":"try {\n  await client.completeAuthorization(input);\n} catch (e) {\n  if (e.code === \"MCP_AUTHORIZATION_CALLBACK_FAILED\") {\n    const [validationError, cleanupError] = e.cause?.errors ?? [];\n    // 1) manually invalidate credentials for connectionId, 2) restart OAuth from a fresh code\n  }\n  throw e;\n}","preventionTips":["Monitor the persistence backend's health before running OAuth callbacks.","Keep the lifecycle deadline long enough to cover exchange + validation + cleanup.","Reconcile orphaned credentials periodically (tokens persisted without a validated session).","Alert on MCP_AUTHORIZATION_CALLBACK_FAILED — it implies two simultaneous failures."],"tags":["oauth","callback","cleanup-failure","aggregate-error"],"backgroundTag":"oauth-credential-cleanup-failed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}