microsoft/playwright · error · Error
Route is already handled!
Error message
Route is already handled!
What it means
A network Route can only be resolved once. The RouteDispatcher._checkNotHandled() method flips an internal _handled flag the first time any of continue/fulfill/abort/redirectNavigationRequest is invoked, and throws on every subsequent call. The browser-side route handler is single-shot by design: a request that has already been continued, fulfilled, or aborted cannot be touched again.
Source
Thrown at packages/playwright-core/src/server/dispatchers/networkDispatchers.ts:143
return { sizes: await this._object.sizes(progress) };
}
}
export class RouteDispatcher extends Dispatcher<Route, channels.RouteChannel, RequestDispatcher> implements channels.RouteChannel {
_type_Route = true;
private _handled = false;
constructor(scope: RequestDispatcher, route: Route) {
super(scope, route, 'Route', {
// Context route can point to a non-reported request, so we send the request in the initializer.
request: scope
});
}
private _checkNotHandled() {
if (this._handled)
throw new Error('Route is already handled!');
this._handled = true;
}
async continue(params: channels.RouteContinueParams, progress: Progress): Promise<channels.RouteContinueResult> {
// Note: progress is ignored because this operation is not cancellable and should not block in the browser anyway.
this._checkNotHandled();
await progress.race(this._object.continue({
url: params.url,
method: params.method,
headers: params.headers,
postData: params.postData,
isFallback: params.isFallback,
}));
}
async fulfill(params: channels.RouteFulfillParams, progress: Progress): Promise<void> {
// Note: progress is ignored because this operation is not cancellable and should not block in the browser anyway.
this._checkNotHandled();View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Add `return` immediately after every route.fulfill()/route.abort()/route.continue() call so no second call can execute.
- Structure the handler as a single if/else-if/else chain where exactly one branch resolves the route.
- If multiple handlers match, narrow URL patterns (glob/regex) so only one matches each request.
- When chaining route.fetch() to inspect the response, call route.fulfill() with that response — do not also call route.continue().
Example fix
// before
await page.route('**/api', route => {
if (cached) route.fulfill({ body: cached });
route.continue(); // throws if cached was true
});
// after
await page.route('**/api', route => {
if (cached) return route.fulfill({ body: cached });
return route.continue();
}); Defensive patterns
Strategy: validation
Validate before calling
// Structure the handler so exactly one route method runs and returns.
await page.route('**/api', async route => {
const req = route.request();
if (req.method() !== 'GET')
return route.continue(); // single call + return
const cached = cache.get(req.url());
if (cached)
return route.fulfill({ body: cached });
return route.continue();
}); Try / catch
// If you cannot guarantee single-call structure, swallow the 'already handled' error.
try {
await route.fulfill({ body: 'ok' });
} catch (e) {
if (!(e instanceof Error) || !e.message.includes('already handled')) throw e;
// route was already resolved elsewhere; safe to ignore
} Prevention
- Always `return` immediately after any route.continue/fulfill/abort call.
- Write route handlers as a single if/else-if/else chain with one resolving branch.
- Avoid overlapping page.route() and context.route() URL patterns.
When it happens
Trigger: Calling route.fulfill() then route.continue() on the same route; calling route.abort() after route.continue(); forgetting a `return` after route.fulfill() in a page.route() handler so execution falls through to a second handler call; two page.route() handlers matching the same URL where both call a route method.
Common situations: Handler written as `if (cond) route.fulfill({...}); route.continue();` without an else/return; calling a route method inside a loop or recursion that re-enters; mixing context.route() with page.route() on overlapping URL patterns so both fire for one request.
Related errors
- Timeout ${options.timeout}ms exceeded
- Route from har is not supported in thin clients
- Malformed endpoint. Did you use BrowserType.launchServer met
- Timeout ${params.timeout}ms exceeded
- Socks4 proxy protocol does not support authentication
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/4777bd4325f9dc5c.
Report an issue: GitHub.