Edge Runtime Systems · Cloudflare Workers

The Intermittent I/O Bug in Code Mode

You turned 21 tools into one code block, cut your upfront context by 88%, and joined PostHog, VaatChitra, and Memory. Then, on run four, tool 2 throws Network connection lost. Here is why it happens, why retrying naively is hazardous, and the two-layer fix.

Target: MCPlex & Cloudflare Workers Edge Execution · 8 min read

01 The symptom in the rehearsal script

In the rehearsal script for your 52% Code Mode talk, there's a telling stage direction under contingencies:

From talks/52-percent-codemode-script

"A codemode call errors (known intermittent Workers I/O bug — fix before Saturday if possible): retry once with 'even the demo believes in retries.'"

Every engineer who builds agents at the edge encounters this exact ghost. You have three tools in your bundle:

  1. PostHog: external REST / MCP endpoint.
  2. VaatChitra: your call recording transcriber on Cloudflare Workers.
  3. Memory: your long-term agent memory server on Cloudflare Workers.

In isolated unit tests, each tool call succeeds 100% of the time. But when the LLM writes a multi-step orchestration script:

const usage = await posthog.query({ project: "site-inspection" });
// ... 4.8 seconds of model reasoning or data crunching ...
const notes = await memory.find({ query: "client expectations" }); <- CRASH: fetch failed

The error arrives as TypeError: fetch failed, Network connection lost, or I/O error: connection closed before response finished. It's intermittent. It vanishes on single-tool runs. Why?

02 The root cause: The Stale Keep-Alive Socket Race

To understand this, look at how the V8 / workerdThe open-source Cloudflare Workers runtime that executes V8 isolates without Node.js operating system processes. network layer handles outbound HTTP connections.

When your Worker makes a subrequest via fetch("https://..."), it doesn't tear down the TCP/TLS connection immediately. It places the socket into an internal Keep-Alive Pool so that subsequent requests to the same host or cluster skip the 60–120ms TLS handshake.

// The Keep-Alive Race Condition

1. PostHog Call 2. Compute / Gap 3. VaatChitra Call fetch() -> TLS Open Socket pooled (idle) 5s idle timeout on edge Remote TCP FIN/RST Reuses Dead Socket!

Here is the trap: Cloudflare edge proxies and Workers frontends have aggressive idle keep-alive timeouts (often exactly 5.0 seconds).

In standard tool calling, every hop is an inference round trip, so fresh connections are negotiated. But in Code Mode, your single script executes multiple subrequests. If step 1 finishes, and data aggregation or a secondary fetch happens right around that 5-second boundary, workerd grabs the pooled socket at the exact instant the remote peer has sent a FIN.

Node.js's undici has auto-retry logic for headers-not-sent keep-alive resets. Workers fetch() does not. It surfaces directly as an unhandled I/O exception.

03 Interactive Simulator: Watch the Socket Die

Adjust the execution gap between Tool 1 and Tool 2. See how standard fetch() behaves against workerd's socket pool, and what happens when the idle timeout strikes:

// workerd Connection Pool State Machine

1. PostHog Tool
READY
Socket Pool (Keep-Alive)
EMPTY
2. VaatChitra / Memory
PENDING
// Output console will show subrequest lifecycle and socket states...

04 The Second Trap: The Uncancelled SSE Body Leak

Under the new 2026-07-28 MCP Streamable HTTP binding, tools can stream progress or return chunked JSON-RPC streams.

If your Code Mode runner reads an MCP response but doesn't explicitly drain or cancel the body:

// NAIVE MCP CALLER IN CODEMODE
const res = await fetch(serverUrl, { method: "POST", body: jsonRpc });
const data = await res.json();
// res.body is parsed, but the underlying HTTP/2 stream reader is NOT explicitly closed!

In Cloudflare Workers, an unclosed ReadableStream keeps the subrequest handle open in the V8 isolate microtask loop. When tool 2 or tool 3 executes, the isolate hits Workers' concurrent subrequest limit (50 subrequests) or stalls because connection slots are pinned.

The Invariant

Every response body in an edge Code Mode runner must be either fully consumed or explicitly canceled via await res.body?.cancel() before the runner resolves.

05 The Two-Layer Solution

You don't need a heavy external retry framework. The fix has two crisp layers:

Layer 1: Internal Servers Become Service Bindings (Zero Network)

Look at your bundle: VaatChitra and Memory are Cloudflare Workers you control!

Calling your own Workers over public HTTPS (https://vaatchitra.prashamhtrivedi.app/mcp) is paying the network tax twice: DNS resolution, edge ingress, TLS termination, and socket pooling.

Cloudflare Workers Service Bindings with RPC (via WorkerEntrypoint) allow direct memory-speed isolate-to-isolate calls:

// In your Worker wrangler.toml:
[[services]]
binding = "VAATCHITRA"
service = "vaatchitra-worker"

[[services]]
binding = "MEMORY"
service = "agent-memory-worker"
MetricOver Public HTTPSVia Service Binding RPC
Latency45ms – 110ms< 0.5ms (in-process)
Socket Re-useStale RST hazardNo sockets / In-memory Cap'n Proto
Subrequest QuotaConsumes 1 of 50Zero (exempt)
Auth VerificationBearer token unwrap per hopDirect capability binding

Layer 2: The Resilient External Subrequest Guard

For external servers like PostHog that must cross public HTTPS, use a targeted wrapper that detects keep-alive socket drops before bytes were accepted and transparently retries with a fresh socket:

/**
 * Safe subrequest caller for Cloudflare Workers Code Mode.
 * - Evicts stale keep-alive sockets on connection reset.
 * - Guaranteed stream drainage to prevent socket pool poisoning.
 */
export async function safeMcpFetch(url: string, init: RequestInit, attempts = 2): Promise<Response> {
  for (let i = 0; i < attempts; i++) {
    try {
      const res = await fetch(url, {
        ...init,
        // Prevent proxy keep-alive staleness if this is a retried attempt
        headers: {
          ...init.headers,
          "Connection": i > 0 ? "close" : "keep-alive",
        }
      });
      return res;
    } catch (err: any) {
      // Catch socket reset before HTTP response headers received
      const isSocketDrop = err?.message?.includes("network connection lost") ||
                           err?.message?.includes("fetch failed");
      if (i < attempts - 1 && isSocketDrop) {
        // Transparent retry with a fresh TCP handshake
        continue;
      }
      throw err;
    }
  }
  throw new Error("Unreachable");
}

06 Check your understanding

An agent script executes tool 1 (PostHog), performs 6 seconds of synchronous data mapping, and then calls tool 2 (VaatChitra over HTTPS). The second call throws a connection error. What happened?

Correct. Standard Workers CPU limit on paid plans is up to 30 seconds of pure CPU and 15 minutes of wall-clock I/O. The failure is a classic keep-alive socket race: the remote edge proxy closed the connection during the 6s idle window, but the local socket pool attempted to reuse the dead file descriptor.

07 Recap

The one sentence, earned

The intermittent I/O bug in edge Code Mode isn't random: it is a keep-alive socket race on sequential subrequests across the 5-second proxy boundary — cured by routing internal servers through Service Bindings RPC and guarding external calls with a transparent RST retry.

Deploy this before Saturday's meetup, and you can strike the contingency line from your rehearsal script: the demo won't need to believe in retries because the socket pool won't lie to it.