{"record":{"id":"afe324c7615e4971","repo":"can1357/oh-my-pi","slug":"no-response-received-for-request-id-expectedid","errorCode":null,"errorMessage":"No response received for request ID ${expectedId}","messagePattern":"No response received for request ID (.+?)","errorType":"exception","errorClass":"MCPTransportError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/mcp/transports/http.ts","lineNumber":616,"sourceCode":"\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// An abrupt drop (socket reset, body-read failure) is as\n\t\t\t\t\t\t// resumable as a server-initiated close once an event ID\n\t\t\t\t\t\t// exists; the request timeout still bounds the total wait.\n\t\t\t\t\t\tif (captured) return;\n\t\t\t\t\t\tif (signal.aborted || resume.lastEventId === null) throw error;\n\t\t\t\t\t\tlogger.debug(\"MCP SSE response stream dropped; resuming\", {\n\t\t\t\t\t\t\turl: this.config.url,\n\t\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tif (captured) return;\n\t\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t\tthrow signal.reason ?? new DOMException(\"MCP SSE response aborted\", \"AbortError\");\n\t\t\t\t\t}\n\t\t\t\t\tif (resume.lastEventId === null) {\n\t\t\t\t\t\tthrow new MCPTransportError({\n\t\t\t\t\t\t\ttransport: \"http\",\n\t\t\t\t\t\t\tstage: \"receive\",\n\t\t\t\t\t\t\tfailure: \"eof\",\n\t\t\t\t\t\t\tmessage: `No response received for request ID ${expectedId}`,\n\t\t\t\t\t\t\tretryable: false,\n\t\t\t\t\t\t\ttraceId,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tcurrent = await this.#fetchSSEResume(resume, signal);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (captured) return;\n\t\t\t\t// The server accepted this POST (it returned a 2xx SSE stream) before\n\t\t\t\t// the drain or a resume GET failed, so the originating request must\n\t\t\t\t// never be replayed — it may already have executed a state-changing\n\t\t\t\t// tool. Preserve SSEResumeError so #requestWithAuthRetry's no-replay\n\t\t\t\t// guard still fires instead of refreshing auth and re-POSTing, and\n\t\t\t\t// force every other post-acceptance failure non-retryable so the","sourceCodeStart":598,"sourceCodeEnd":634,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/transports/http.ts#L598-L634","documentation":"The transport POSTed a JSON-RPC request, the server accepted it and opened an SSE stream, but the stream ended (EOF) without ever delivering a JSON-RPC response matching the expected request id, and no event id was supplied so the stream cannot be resumed. The transport deliberately does not replay the POST because the request may already have executed a state-changing tool on the server; it throws a non-retryable receive-stage error instead.","triggerScenarios":"Server closes the SSE stream after processing the tool but before writing the response; server crashes mid-request; stream interrupted on first physical connection with no SSE id: field ever sent; handler silently fails without sending a reply.","commonSituations":"MCP server bugs where an exception in the tool handler drops the response; server timeouts shorter than the client's; network cut on the first event frame; servers behind proxies that sever idle streams before the response is emitted.","solutions":["Check the MCP server logs around the trace id for a crash or unhandled error in the request handler and fix the handler to always emit a JSON-RPC response.","Verify whether the side effect (tool call) actually executed; if the operation is idempotent, re-issue the request explicitly.","Increase server-side request timeouts so the server does not close the stream before the handler completes.","Fix proxies to keep streaming connections alive (proxy_read_timeout, disable idle connection reaping).","Update the MCP server — every accepted request must produce exactly one response message on the SSE stream."],"exampleFix":"// server handler (before): early return skips sending the JSON-RPC reply\nif (!resource) return;\nsendResult(id, result);\n// after: always answer the pending request\nif (!resource) { sendError(id, { code: -32602, message: 'not found' }); return; }\nsendResult(id, result);","handlingStrategy":"try-catch","validationCode":"// no pre-call check can detect a dropped response; monitor stream health instead\nconst ok = await fetch(url, { method: 'OPTIONS' }).then(r => r.ok).catch(() => false);\nif (!ok) throw new Error('MCP endpoint unreachable — skip request');","typeGuard":"function isMissingResponseError(e: unknown): e is MCPTransportError {\n  return e instanceof MCPTransportError && e.failure === 'eof' && e.stage === 'receive' &&\n    /No response received for request ID/.test(e.message);\n}","tryCatchPattern":"try {\n  result = await client.request('tools/call', { name, args });\n} catch (e) {\n  if (isMissingResponseError(e)) {\n    // request MAY have executed; check server state before re-running\n    result = await confirmOrReplayIdempotent(name, args);\n  } else throw e;\n}","preventionTips":["Design tools to be idempotent where possible so unknown-outcome requests can be safely re-issued.","Set server-side timeouts longer than the client's.","Alert on MCP server handler exceptions so responses are never silently dropped.","Keep proxies from severing slow SSE streams (raise read timeouts)."],"tags":["mcp","http","sse","json-rpc","missing-response"],"backgroundTag":"missing-json-rpc-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}