different-ai/openwork · error · ProbeFailure

CONTINUITY_SESSION

CONTINUITY_SESSION

Error message

MCP session expired after initialize

What it means

Thrown when the notifications/initialized POST (sent right after a successful initialize) returns HTTP 404. In streamable-HTTP MCP, a 404 on a post-initialize request signals the server no longer recognizes the session, so the probe classifies it as a session-expiry continuity failure rather than a generic lifecycle error.

Source

Thrown at packages/enterprise-mcp-mock-server/src/testing/probe.ts:786

    if (scenario.protocol.requireSession && !sessionId) {
      throw new ProbeFailure("MCP_INITIALIZE", "mcp_initialize", "Initialize response omitted required MCP-Session-Id")
    }
    recordPassed(phases, "MCP_INITIALIZE", startedAt, "MCP version, capabilities, and session negotiated")

    const sessionHeaders = {
      ...rpcHeaders,
      "mcp-session-id": sessionId,
      "mcp-protocol-version": initialize.protocolVersion,
    }
    startedAt = Date.now()
    const initializedResponse = await fetchStep(mcpUrl, {
      method: "POST",
      headers: sessionHeaders,
      body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }),
    }, "MCP_INITIALIZED", overallDeadline)
    await discardResponseBody(initializedResponse, "MCP_INITIALIZED", "mcp_lifecycle")
    if (initializedResponse.status === 404) {
      throw new ProbeFailure("CONTINUITY_SESSION", "mcp_session_expired", "MCP session expired after initialize")
    }
    if (initializedResponse.status !== 202) {
      throw new ProbeFailure("MCP_INITIALIZED", "mcp_lifecycle", `Initialized notification returned HTTP ${initializedResponse.status}`)
    }
    recordPassed(phases, "MCP_INITIALIZED", startedAt, "Initialized notification received HTTP 202")

    startedAt = Date.now()
    const toolNames = new Set<string>()
    const discoveredTools: MockTool[] = []
    const cursors = new Set<string>()
    let cursor: string | undefined
    let catalogComplete = false
    for (let page = 0; page < 25; page += 1) {
      if (cursor) {
        if (cursors.has(cursor)) throw new ProbeFailure("MCP_TOOL_DISCOVERY", "mcp_pagination_loop", "Tool catalog repeated a cursor")
        cursors.add(cursor)
      }
      const listRawResponse = await fetchStep(mcpUrl, {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Ensure the server persists sessions for the probe's lifetime (or long enough TTL)
  2. Use sticky sessions / a shared session store when running multiple server instances
  3. Verify the server routes on mcp-session-id exactly as issued during initialize
  4. Retry the probe against a single warm server instance to rule out restarts

Example fix

// before (server)
const session = sessions.get(req.headers["mcp-session-id"]) ?? null // 404 if lost
// after (server)
persistSessionInSharedStore(sessionId, session) // e.g. Redis
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

try {
  await probeEnterpriseMcpMockServer(scenario)
} catch (e) {
  if (e instanceof ProbeFailure && e.code === "CONTINUITY_SESSION") {
    console.error("Session expired mid-probe:", e.message) // re-init or use sticky sessions
  }
}

Prevention

When it happens

Trigger: The server invalidates the session between initialize and notifications/initialized, or never persisted it, so the notification with the mcp-session-id header hits an unknown session and the server answers 404.

Common situations: In-memory session store lost between requests (multi-instance deployment without sticky routing/session affinity); very short session TTL; server restart during the probe; session store keyed incorrectly so the id never matches.

Related errors


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