Skip to content

HTTP bridge: late write after -32000 Request timeout on cold memory_store → ERR_HTTP_HEADERS_SENT; timeout not configurable #3158

Description

@ruvnet

HTTP transport: after a tools/call hits the server-side -32000 Request timeout, the handler still tries to answer → ERR_HTTP_HEADERS_SENT in the log

Version: ruflo@3.38.20 (ruflo mcp start -t http), Node 22, Linux container. Observed on a private bridge image (cognitum-one/ruclip) with --tools set to an exact-name allowlist.

What happened

  1. First memory_store on a fresh process (embedding model not yet loaded in-process) was answered by the server after exactly 30.008 s with:
    {"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Request timeout"}}
  2. The handler then completed the store anyway (the value was retrievable afterwards) and attempted to write its real response to the same, already-finished ServerResponse. The process logged:
    Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
        at ServerResponse.setHeader (node:_http_outgoing:700:11)
        at Layer.handleError (/app/node_modules/router/lib/layer.js:116:17)
    
  3. A second memory_store ~1 min later took 24.95 s and succeeded; every store after that was 13–15 ms. So the timeout path is racing the lazy embedder load, and the late write is what trips the router.

Why it matters

  • Every store after the first is fine, so this only shows on cold start — but it is exactly what a Cloud Run startup probe sees, and the stray write is a silent second response path (the client already got -32000; nothing tells it the store actually persisted).
  • The 30 s timeout is not configurable from the CLI as far as we can find, and it is shorter than a cold ONNX session init on a slow host.

Suggested fix

  • Cancel/detach the handler when the timeout fires (or mark the response as sent and skip the late setHeader), and
  • expose the request timeout as a flag/env (--request-timeout-ms) so hosted bridges can set it above their measured cold-start.

Workaround we shipped: a readiness wrapper that keeps the real process warm and does not open the public port until an in-process memory_store has succeeded (cognitum-one/ruclip, ADR-0002 "Cold start").

Related: #3155 (bridge state loss), #3157 (per-session rate limit).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions