AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

Request APIs After An Await Throw E1378 On Client Disconnect — But Only If `After()` Was Called Earlier In The Request

Calling `after()` arms the current work-unit store, causing the `phase` to be flipped to `'after'` on response close. If the page component later resumes from an await and calls `headers()` or `cookies()`, it gets rejected with E1378, even though the request data is still available. The error is non-deterministic and depends on a race between client disconnect and async continuation.

highConfidence 85%Next.jsAffected V16.3.3

Origin Analysis

`AfterContext.after()` registers the current work-unit store. On response close, `onClose` sets `phase = 'after'` only for stores that called `after()`. When a page component resumes after the response has closed, its `phase` is already `'after'`, causing `isRequestApiAllowedInCurrentPhase` to reject request APIs. The error is conditional on both arming and a race, making it hard to reproduce and attribute.
1. Create two identical dynamic routes, one calling `after(() => {})` before an await and one without. 2. Use `curl --max-time 1` to abort requests to both routes. 3. Observe server logs: the route with `after()` logs E1378 after ~5s, while the other does not.

Fixing Code Block

Edge Case Audit

This change alters the contract: previously, any use of request APIs in a page after response close would throw if `after()` was called. With this fix, such uses are allowed. This could hide real bugs if developers rely on the error to detect late access in scenarios where data might be stale. However, headers and cookies are immutable during a request, so the risk is minimal. Ensure that the `WorkUnitStore` type includes `'after'` in your version; if not, adjust the type guard accordingly. Rollback: revert this function to the original implementation (reject all `phase === 'after'` stores) if unexpected regressions occur.

Ecosystem Topology