Workers SDK Issue Reports

← Back to Dashboard

#7356 Error loop/hang with passThroughOnException and no host

Download Reproduction
Recommendation:KEEP OPEN
Difficulty:medium
Reasoning:

Bug confirmed in wrangler 4.60.0. passThroughOnException() without --host causes infinite loop because no upstream to fall back to.

Suggested Action:

Implement detection of no-upstream case and return helpful 500 error

Analysis Report

Issue Review: cloudflare/workers-sdk#7356

Summary

Using passThroughOnException() in wrangler dev without a --host causes an infinite error loop and request hang because there's no upstream origin to fall back to.

Findings

  • Created: 2024-11-26
  • Updated: 2025-10-30
  • Version: 3.91.0 -> 4.60.0
  • Component: wrangler (dev mode), miniflare
  • Labels: bug
  • Comments: 0

Key Evidence

  • Bug successfully reproduced on wrangler 4.60.0 (current version)
  • No PRs reference fixing this issue
  • No changelog entries mention this issue
  • Issue remains open with no comments after 14 months

Reproduction Results

With wrangler dev --no-bundle (no --host):

  • Single curl request triggers 200+ error messages in rapid succession
  • Curl hangs indefinitely until timeout
  • Error loop continues until process is killed

Root Cause Analysis

The bug occurs due to how passThroughOnException() interacts with workerd's fail-open mechanism:

  1. Worker calls ctx.passThroughOnException() - This sets failOpen = true in the IoContext (src/workerd/io/io-context.h:376)

  2. Worker throws an exception - In this case, throw new Error("bang")

  3. workerd attempts fail-open fallback (src/workerd/io/worker-entrypoint.c++:460-470):

    } else KJ_IF_SOME(service, failOpenService) {
      // Fall back to origin.
      auto promise = kj::evalNow([&] {
        auto promise = service.get()->request(method, url, headers, requestBody, *wrappedResponse);
        // ...
      });
    }
    
  4. Without --host, the fail-open service loops back to the same worker - The NEXT_CLIENT_CHANNEL resolves to miniflare's entry worker, which then routes back to the user worker, triggering the same exception again.

  5. Infinite loop - Each iteration throws, triggers fail-open, which calls the same worker again.

Code References

workerd (worker-entrypoint.c++):

  • Lines 358-363: failOpenService is set when isFailOpen() is true
  • Lines 460-470: Fail-open fallback attempts to forward request to failOpenService
  • Line 785 (io-context.h): NEXT_CLIENT_CHANNEL definition

miniflare (entry.worker.ts):

  • The entry worker receives fail-open requests but has no way to distinguish them from normal requests
  • Without an upstream configured, requests loop back through the same routing logic

Recommendation

Status: KEEP OPEN

Reasoning: Bug is confirmed present in the latest wrangler version (4.60.0). This is a valid developer experience issue that causes wrangler dev to become unresponsive when using passThroughOnException() without configuring an upstream host.

Action: Implement a fix to detect and handle the no-upstream case gracefully.

Proposed Solution

Option 1: Return 500 error when no upstream is configured (Recommended)

Detect when passThroughOnException() is triggered but no upstream exists, and return a clear error response instead of looping.

Implementation in packages/miniflare/src/workers/core/entry.worker.ts:

// In the catch block of the fetch handler, check for passthrough scenario
// Add a header or mechanism to detect passthrough requests

// Before routing to service, check if this is a passthrough retry without upstream
const isPassThroughRetry = request.headers.get(CoreHeaders.PASS_THROUGH_RETRY) !== null;
const hasUpstream = env[CoreBindings.TEXT_UPSTREAM_URL] !== undefined;

if (isPassThroughRetry && !hasUpstream) {
  return new Response(
    "passThroughOnException() was triggered, but no upstream origin is configured.\n" +
    "Use --host <hostname> with wrangler dev to specify a fallback origin.",
    { status: 500 }
  );
}

Add new header constant in packages/miniflare/src/workers/core/constants.ts:

export const CoreHeaders = {
  // ... existing headers
  PASS_THROUGH_RETRY: "MF-Pass-Through-Retry",
} as const;

Option 2: Log warning and return 500 immediately

If modifying workerd is acceptable, add logic to check for upstream availability before setting failOpen:

// In worker-entrypoint.c++
if (context.isFailOpen() && hasUpstream()) {
  failOpenService = context.getSubrequestChannelNoChecks(...);
}

Option 3: Rate-limit passthrough retries

Add a counter/flag to prevent infinite loops by limiting retry attempts.

Implementation Details

Files to Modify

  1. packages/miniflare/src/workers/core/constants.ts

    • Add PASS_THROUGH_RETRY header constant
  2. packages/miniflare/src/workers/core/entry.worker.ts

    • Add detection logic for passthrough without upstream
    • Return meaningful error response
  3. packages/wrangler/src/dev/miniflare/index.ts (possibly)

    • Ensure upstream configuration is properly passed to miniflare options
  4. Tests:

    • packages/miniflare/test/ - Add test for passthrough behavior without upstream

Implementation Difficulty: Medium

Justification:

  • The fix requires understanding the request flow between workerd, miniflare, and the entry worker
  • Need to carefully add header tracking without breaking existing passthrough functionality
  • The core logic change is small, but testing all edge cases requires understanding the full dev mode architecture
  • May need coordination with workerd team if deeper fix is required

Testing Recommendations

  1. Unit test: Verify that passthrough requests without upstream return 500 with helpful message
  2. Integration test: Run wrangler dev without --host, use passThroughOnException, verify no hang
  3. Regression test: Verify passThroughOnException still works correctly with --host specified
  4. Edge cases:
    • Multiple passThroughOnException calls
    • passThroughOnException with partial response already sent
    • passThroughOnException in different handler types (fetch, scheduled, etc.)

Suggested Comment

Hi! I've verified this bug is still present in wrangler 4.60.0. The root cause is that passThroughOnException() triggers workerd's fail-open mechanism, which attempts to forward the request to an upstream origin. Without --host specified, there's no upstream, so the request loops back to the same worker indefinitely.

A potential fix would be to detect this scenario in miniflare's entry worker and return a helpful 500 error instead of looping. Would the team be open to a PR addressing this?

Notes & Feedback (0)

No notes yet.

Add Note