grafana/k6 · error
route is already handled
Error message
route is already handled
What it means
A Route must be completed exactly once with one of its terminal operations (continue, fulfill, abort). startHandling() flips a one-shot handled flag and every terminal operation goes through it, so any second completion attempt on the same Route fails with this error. The first call wins; all later ones are rejected.
Source
Thrown at internal/js/modules/k6/browser/common/http.go:909
return err
}
return r.networkManager.ContinueRequest(r.request.interceptionID, opts, r.request.HeadersArray())
}
// Fulfill fulfills the request with the given options for the response.
func (r *Route) Fulfill(opts FulfillOptions) error {
err := r.startHandling()
if err != nil {
return err
}
return r.networkManager.FulfillRequest(r.request, opts)
}
func (r *Route) startHandling() error {
if r.handled {
return fmt.Errorf("route is already handled")
}
r.handled = true
return nil
}
View on GitHub (pinned to 93accf6570)
Solutions
- Ensure exactly one terminal call (continue|fulfill|abort) executes per route: return immediately after it
- Restructure the handler to compute the decision first, then perform a single terminal call at one exit point
- Never wrap fulfill/abort/continue in retry loops that may re-invoke them
- Add a local handled flag in complex handlers to make the one-shot rule explicit
Example fix
// before
route.fulfill({ status: 200, body: 'ok' });
route.fulfill({ status: 200, body: 'ok' }); // throws
// after
let done = false;
if (!done) { done = true; route.fulfill({ status: 200, body: 'ok' }); } Defensive patterns
Strategy: validation
Validate before calling
let handled = false;
async function complete(route, decision) {
if (handled) return;
handled = true;
if (decision === 'fulfill') await route.fulfill(opts);
else if (decision === 'abort') await route.abort();
else await route.continue();
} Try / catch
try { await route.fulfill(opts); }
catch (e) { if (!/route is already handled/.test(String(e.message))) throw e; /* first terminal call already won */ } Prevention
- Call exactly one of continue/fulfill/abort per route, then return
- Never put terminal calls in retry loops or try/finally double-paths
- Track completion with your own flag in branching handlers
When it happens
Trigger: Calling route.fulfill() twice; calling route.continue() and then route.fulfill(); handler logic where multiple branches each perform a terminal call; retry wrappers or try/finally blocks that re-invoke fulfill/abort after the first call already resolved.
Common situations: page.route() handlers with branching async logic that double-resolves; copy-pasted handler templates that abort on error even after fulfilling; wrapping terminal calls in generic retry helpers; handler chains where a timeout path races the happy path.
Related errors
- creating url matcher for path %s: %w
- predicate function is not callable
- errorText
- missing presigned url in response body
- unknown browser event: %q, must be %q
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/f52d0df400b5739a.
Report an issue: GitHub.