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

  1. Retry the whole connect/completeAuthorization flow with a fresh lifecycle — a new authorization transaction is required after a deadline abort.
  2. Increase the lifecycle expiresAt window (operationTimeoutMs / lifecycle option) to cover slow discovery + registration + token exchange.
  3. Check what called lifecycle.signal.abort() and whether it fires prematurely (request cancellation, page unload, parent timeout).
  4. 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

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


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/74266bbd2d197895. Report an issue: GitHub.