different-ai/openwork · error · EnterpriseMcpOAuthContractError
MCP_LIFECYCLE_DEADLINE
MCP_LIFECYCLE_DEADLINE
Error message
The enterprise MCP lifecycle expired before OAuth persistence could continue.
What it means
context() checks the lifecycle before every OAuth persistence operation: if the lifecycle AbortSignal is aborted or clock.now() has reached lifecycle.expiresAt, it throws EnterpriseMcpOAuthContractError with code MCP_LIFECYCLE_DEADLINE. The operation ran out of its allotted time budget (or was cancelled), so the library refuses to continue persisting OAuth state rather than commit work past the deadline.
Source
Thrown at packages/enterprise-mcp-client/src/oauth-provider.ts:122
this.connectionId = input.connectionId
this.persistence = input.persistence
this.flow = input.flow
this.clientName = input.clientName
this.clock = input.clock
this.lifecycle = input.lifecycle
this.authorizationTransactionTtlMs = input.authorizationTransactionTtlMs
this.expirationSkewMs = input.expirationSkewMs
this.applicationType = input.oauthConfiguration?.applicationType ?? "web"
this.clientMetadataUrl = input.oauthConfiguration?.clientMetadataUrl
this.authorizationServerIssuer = input.oauthConfiguration?.authorizationServerIssuer
this.requestedScopes = [...new Set(input.oauthConfiguration?.requestedScopes ?? [])]
this.fetch = input.fetch
}
private context(): EnterpriseMcpPersistenceContext {
const now = this.clock.now()
if (this.lifecycle.signal.aborted || now >= this.lifecycle.expiresAt) {
throw new EnterpriseMcpOAuthContractError(
"MCP_LIFECYCLE_DEADLINE",
"The enterprise MCP lifecycle expired before OAuth persistence could continue.",
)
}
return {
connectionId: this.connectionId,
commitExpiresAt: this.lifecycle.expiresAt,
signal: this.lifecycle.signal,
}
}
get redirectUrl(): string {
return this.redirectUri
}
state(): string {
if (this.flow.kind !== "connect" || !this.flow.authorizationId) {
throw new EnterpriseMcpOAuthContractError(View on GitHub (pinned to 2b7df46e8a)
Solutions
- Retry the whole connect/completeAuthorization flow with a fresh lifecycle — a new authorization transaction is required after a deadline abort.
- Increase the lifecycle expiresAt window (operationTimeoutMs / lifecycle option) to cover slow discovery + registration + token exchange.
- Check what called lifecycle.signal.abort() and whether it fires prematurely (request cancellation, page unload, parent timeout).
- Speed up or cache discovery/registration (persisted discovery state, pre-registered client) so the flow fits the budget.
Example fix
// before
const lifecycle = { signal: controller.signal, expiresAt: clock.now() + 5_000 }
// after
const lifecycle = { signal: controller.signal, expiresAt: clock.now() + 30_000 } Defensive patterns
Strategy: retry
Validate before calling
function lifecycleHasBudget(lifecycle, clock) {
return !lifecycle.signal.aborted && clock.now() < lifecycle.expiresAt;
}
// check before starting any OAuth flow Type guard
function isLifecycleDeadlineError(e: unknown): e is { code: "MCP_LIFECYCLE_DEADLINE" } {
return typeof e === "object" && e !== null && (e as { code?: string }).code === "MCP_LIFECYCLE_DEADLINE";
} Try / catch
try {
await client.connect(input);
} catch (e) {
if (e.code === "MCP_LIFECYCLE_DEADLINE") {
// restart the flow with a fresh, longer-lived lifecycle and a new authorizationId
return client.connect({ ...input, authorizationId: newSignedId() });
}
throw e;
} Prevention
- Size the lifecycle window to cover discovery + registration + redirect + callback with slack.
- Never reuse an expired/aborted lifecycle for a new flow — create a fresh one per attempt.
- Audit abort() callers so the signal isn't cancelled prematurely.
- Cache/persist discovery state and use pre-registered clients to shorten flows.
When it happens
Trigger: Any persistence-touching provider method (discoveryState, saveDiscoveryState, clientInformation, tokens, saveTokens, saveCodeVerifier, invalidateCredentials, etc.) invoked after the lifecycle deadline passed or after lifecycle.signal.abort() — e.g. a long OAuth discovery/registration flow exceeding the operation timeout, or an external abort triggered mid-flow.
Common situations: Slow authorization servers making dynamic client registration + discovery exceed the lifecycle window; users leaving the OAuth page open too long before completing the callback; upstream code aborting the signal on request cancellation; very tight lifecycle timeouts configured for large discovery chains.
Related errors
- Request timed out.
- Authorization for ${input.connectionName} did not finish. Co
- Agent context diagnostics timeout must be between 1 ms and 3
- Timed out waiting for server health
- t("providers.not_connected")
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/74266bbd2d197895.
Report an issue: GitHub.