{"record":{"id":"f52d0df400b5739a","repo":"grafana/k6","slug":"route-is-already-handled","errorCode":null,"errorMessage":"route is already handled","messagePattern":"route is already handled","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/http.go","lineNumber":909,"sourceCode":"\t\treturn err\n\t}\n\n\treturn r.networkManager.ContinueRequest(r.request.interceptionID, opts, r.request.HeadersArray())\n}\n\n// Fulfill fulfills the request with the given options for the response.\nfunc (r *Route) Fulfill(opts FulfillOptions) error {\n\terr := r.startHandling()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn r.networkManager.FulfillRequest(r.request, opts)\n}\n\nfunc (r *Route) startHandling() error {\n\tif r.handled {\n\t\treturn fmt.Errorf(\"route is already handled\")\n\t}\n\tr.handled = true\n\treturn nil\n}\n","sourceCodeStart":891,"sourceCodeEnd":914,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/http.go#L891-L914","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nroute.fulfill({ status: 200, body: 'ok' });\nroute.fulfill({ status: 200, body: 'ok' }); // throws\n\n// after\nlet done = false;\nif (!done) { done = true; route.fulfill({ status: 200, body: 'ok' }); }","handlingStrategy":"validation","validationCode":"let handled = false;\nasync function complete(route, decision) {\n  if (handled) return;\n  handled = true;\n  if (decision === 'fulfill') await route.fulfill(opts);\n  else if (decision === 'abort') await route.abort();\n  else await route.continue();\n}","typeGuard":null,"tryCatchPattern":"try { await route.fulfill(opts); }\ncatch (e) { if (!/route is already handled/.test(String(e.message))) throw e; /* first terminal call already won */ }","preventionTips":["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"],"tags":["browser","routing","network","api-misuse","request-interception"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}