#7356 Error loop/hang with passThroughOnException and no host
Bug confirmed in wrangler 4.60.0. passThroughOnException() without --host causes infinite loop because no upstream to fall back to.
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:
Worker calls
ctx.passThroughOnException()- This setsfailOpen = truein the IoContext (src/workerd/io/io-context.h:376)Worker throws an exception - In this case,
throw new Error("bang")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); // ... }); }Without
--host, the fail-open service loops back to the same worker - TheNEXT_CLIENT_CHANNELresolves to miniflare's entry worker, which then routes back to the user worker, triggering the same exception again.Infinite loop - Each iteration throws, triggers fail-open, which calls the same worker again.
Code References
workerd (worker-entrypoint.c++):
- Lines 358-363:
failOpenServiceis set whenisFailOpen()is true - Lines 460-470: Fail-open fallback attempts to forward request to
failOpenService - Line 785 (io-context.h):
NEXT_CLIENT_CHANNELdefinition
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
packages/miniflare/src/workers/core/constants.ts- Add
PASS_THROUGH_RETRYheader constant
- Add
packages/miniflare/src/workers/core/entry.worker.ts- Add detection logic for passthrough without upstream
- Return meaningful error response
packages/wrangler/src/dev/miniflare/index.ts(possibly)- Ensure upstream configuration is properly passed to miniflare options
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
- Unit test: Verify that passthrough requests without upstream return 500 with helpful message
- Integration test: Run wrangler dev without --host, use passThroughOnException, verify no hang
- Regression test: Verify passThroughOnException still works correctly with --host specified
- 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--hostspecified, 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.