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
- Ensure the server persists sessions for the probe's lifetime (or long enough TTL)
- Use sticky sessions / a shared session store when running multiple server instances
- Verify the server routes on mcp-session-id exactly as issued during initialize
- 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
- Use sticky sessions or a shared session store across server instances
- Set session TTL comfortably above probe duration
- Avoid server restarts during probe runs
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
- MCP_SESSION_NOT_FOUND
- MCP_HTTP_404
- invalid_mcp_token_payload
- invalid_mcp_connection_payload
- OpenWork server cannot read MCP config for this workspace.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/849e10fc7b773658.
Report an issue: GitHub.