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

  1. Ensure exactly one terminal call (continue|fulfill|abort) executes per route: return immediately after it
  2. Restructure the handler to compute the decision first, then perform a single terminal call at one exit point
  3. Never wrap fulfill/abort/continue in retry loops that may re-invoke them
  4. 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

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


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/f52d0df400b5739a. Report an issue: GitHub.