Skip to content

Outbound fetch stalls ~30s on requests with Expect: 100-continue instead of sending the body (breaks AWS SDK S3 PutObject under nodejs_compat) #6845

Description

@lukaso

Summary

When a Worker makes an outbound fetch() whose request carries Expect: 100-continue and a body, workerd stalls for ~30 s (and on a current compatibility_date, indefinitely) before sending the body, then the request completes normally. Browser fetch and the Fetch standard do not block on the interim 100 Continue — they just send the body. workerd should match that.

Why this affects a lot of people

nodejs_compat makes libraries bundle their node build. AWS SDK JS v3's @aws-sdk/middleware-expect-continue adds Expect: 100-continue to bodied S3 PutObject/UploadPart requests, gated on runtime === "node":

if (HttpRequest.isInstance(request) && request.body && options.runtime === "node") {
  request.headers = { ...request.headers, Expect: "100-continue" };
}

On Workers the node build runs over a fetch transport (@smithy/fetch-http-handler, since there is no Node http), so every S3 PUT hangs ~30 s. This is the exact stack Cloudflare's own R2 docs recommend (@aws-sdk/client-s3 + FetchHttpHandler), so it hits Workers + R2/S3 users broadly — in wrangler dev and in production. It's intermittent because a warm/reused connection sometimes skips the handshake ("works once or twice, then hangs").

Steps to reproduce

A Worker issuing sequential PutObjectCommands against R2 (or any S3 endpoint) with requestHandler: new FetchHttpHandler(). The first (cold-connection) bodied PUT stalls. A raw fetch() PUT to the same host is ~1 ms, so it is not the network:

request time
raw fetch() PUT (no SDK) ~1 ms
bodyless HeadBucket (no Expect) ~15 ms
SDK PutObject, Expect: 100-continue present (compat 2025-04-02) 30,264 ms
SDK PutObject, Expect present (compat 2026-06-23, workerd 1.20260623.1) >120,000 ms — never completed
SDK PutObject, Expect header stripped ~5 ms (MinIO) / ~200 ms (R2)

Reproduces identically on wrangler dev (miniflare/workerd) and deployed Workers. Notably, on a current compatibility_date the stall is worse — the PUT did not complete within 120 s.

Expected behavior

Outbound fetch should follow browser/Fetch semantics for Expect: 100-continue: send the body without blocking on an interim 100 (browsers effectively ignore the header). The node:http docs already note Expect: 100-continue / the continue event are unsupported — but "unsupported" should mean ignored, not a multi-second (or unbounded) silent stall.

Workaround

Strip the (unsigned → signature-safe) Expect header before transmission. For AWS SDK JS v3:

client.middlewareStack.add(
  (next) => (args) => {
    const headers = args.request?.headers;
    if (headers) {
      for (const k of Object.keys(headers)) {
        if (k.toLowerCase() === "expect") delete headers[k];
      }
    }
    return next(args);
  },
  { step: "finalizeRequest", name: "stripExpectContinue" }
);

Environment

  • workerd 1.20260623.1 (and earlier); reproduced in wrangler dev and production
  • compatibility_date tested: 2025-04-02 (~30 s) and 2026-06-23 (>120 s); compatibility_flags: ["nodejs_compat"]
  • @aws-sdk/client-s3@3.873.0, @smithy/fetch-http-handler@5.x, @astrojs/cloudflare@13, Node 22 host

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions