{"record":{"id":"818339056bc93a45","repo":"different-ai/openwork","slug":"connection-closed-before-the-tool-response-complet","errorCode":null,"errorMessage":"Connection closed before the tool response completed","messagePattern":"Connection closed before the tool response completed","errorType":"exception","errorClass":"ProbeFailure","httpStatus":null,"severity":"error","filePath":"packages/enterprise-mcp-mock-server/src/testing/probe.ts","lineNumber":886,"sourceCode":"            const mutationRequired = fault?.effect === \"commit-then-disconnect\"\n            const tool = discoveredTools.find((candidate) => candidate.kind === (mutationRequired ? \"mutation\" : \"read\"))\n            if (!tool) throw new ProbeFailure(\"MCP_TOOL_EXECUTION\", \"mcp_tool\", \"No suitable tool was available for the requested probe\")\n            return { name: tool.name, arguments: defaultArguments(tool.inputSchema) }\n          })()\n      const selectedTool = profile.tools.find((tool) => tool.name === selected.name)\n      if (mode === \"safe-read\" && selectedTool?.kind !== \"read\") {\n        throw new ProbeFailure(\"CONFIGURATION\", \"configuration\", \"safe-read mode accepts only a declared read-only tool\")\n      }\n      startedAt = Date.now()\n      let toolResponse: Response\n      try {\n        toolResponse = await fetchStep(mcpUrl, {\n          method: \"POST\",\n          headers: sessionHeaders,\n          body: JSON.stringify({ jsonrpc: \"2.0\", id: 100, method: \"tools/call\", params: selected }),\n        }, \"MCP_TOOL_EXECUTION\", overallDeadline)\n      } catch {\n        throw new ProbeFailure(\n          selectedTool?.kind === \"mutation\" ? \"PROVIDER_EXECUTION\" : \"MCP_TOOL_EXECUTION\",\n          selectedTool?.kind === \"mutation\" ? \"mutation_indeterminate\" : \"mcp_tool\",\n          \"Connection closed before the tool response completed\",\n        )\n      }\n      const envelope = await parseRpc(await expectOk(toolResponse, \"MCP_TOOL_EXECUTION\"), \"MCP_TOOL_EXECUTION\")\n      if (envelope.id !== 100) {\n        throw new ProbeFailure(\"MCP_TOOL_EXECUTION\", \"mcp_tool\", \"tools/call response JSON-RPC id did not match the request\")\n      }\n      if (envelope.error) throw new ProbeFailure(\"MCP_TOOL_EXECUTION\", \"mcp_tool\", envelope.error.message)\n      const toolResult = parseAt(\n        z.object({ isError: z.boolean(), structuredContent: z.unknown().optional() }),\n        envelope.result,\n        \"MCP_TOOL_EXECUTION\",\n        \"mcp_tool\",\n        \"Tool response did not match the MCP result shape\",\n      )\n      if (toolResult.isError) {","sourceCodeStart":868,"sourceCodeEnd":904,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/enterprise-mcp-mock-server/src/testing/probe.ts#L868-L904","documentation":"The tools/call POST is wrapped so that any transport failure before a response arrives is converted into a deterministic probe failure. For mutation tools the probe reports PROVIDER_EXECUTION / mutation_indeterminate (the commit may or may not have happened); for everything else it reports MCP_TOOL_EXECUTION / mcp_tool with this message.","triggerScenarios":"The fetch of POST tools/call (id 100) with sessionHeaders throws or times out against the overallDeadline — connection reset, server crashed mid-call (e.g. commit-then-disconnect fault), or deadline exceeded before headers arrive.","commonSituations":"Mock server deliberately disconnects after committing (commit-then-disconnect fault scenario); server not listening on mcpUrl; network proxy dropping keep-alive connections; overallDeadline too tight for a slow tool.","solutions":["If testing the commit-then-disconnect fault, expect mutation_indeterminate and assert the downstream commit/idempotency behavior instead of treating it as a bug.","Verify the mock server is running and mcpUrl/sessionHeaders are correct and current.","Increase overallDeadline if the tool legitimately takes longer than the budget.","Make the mutation tool idempotent so a retry after an indeterminate failure is safe."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// before calling the tool, confirm the endpoint is reachable:\nconst ping = await fetch(mcpUrl, { method: \"POST\", headers: sessionHeaders, body: JSON.stringify({ jsonrpc: \"2.0\", id: 0, method: \"ping\" }) });\nif (!ping.ok) throw new Error(`MCP endpoint unreachable: ${ping.status}`);","typeGuard":null,"tryCatchPattern":"try {\n  await probeEnterpriseMcpMockServer(options);\n} catch (e) {\n  if (e instanceof ProbeFailure && e.reason === \"mutation_indeterminate\") {\n    // check downstream side effects before deciding pass/fail; do not auto-retry mutations\n  } else if (e instanceof ProbeFailure && e.reason === \"mcp_tool\" && e.message.includes(\"Connection closed\")) {\n    // verify server health and deadline, then retry once for read-only calls\n  } else throw e;\n}","preventionTips":["Budget overallDeadline generously relative to the slowest tool.","Keep the mock server alive for the entire probe session.","Make mutation tools idempotent so indeterminate outcomes are recoverable.","Reuse session headers exactly as issued during initialize."],"tags":["mcp","network","timeout","indeterminate-state"],"backgroundTag":"connection-closed-mid-request","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}