▚▚ fix(http): treat end(callback) argument as a callback, not body data
In RECORD mode, calling req.end(callback) on an instrumented outbound http/https request throws TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer ... Received function from the user's own call site, so the request never goes out. On the inbound side, a handler that ends with res.write(body); res.end(callback) still responds normally, but the recorded server span is exported without its response body and an error is logged. Both are the documented Node signature end([data[, encoding]][, callback]).
Root cause
The record-mode patches on ServerResponse.end (http/Instrumentation.ts) and ClientRequest.end (_captureClientRequestBody) only check if (chunk) before treating the first argument as body data. A callback-only end() therefore pushes the function into the chunk list:
- server:
combineChunks()callsBuffer.from(fn)inside theprocess.nextTickbody processing; it throws, is caught and logged, andoutputValue.bodyis never set. - client:
Buffer.from(chunk, encoding)throws synchronously inside the patchedreq.end, propagating to the caller.
The client-side response end listener already guards with typeof chunk !== "function", and the replay-side TdMockClientRequest.end() handles the callback form correctly; the two record-mode end() patches (and the identical one in the Next.js instrumentation) were missed.
Fix
src/instrumentation/libraries/http/Instrumentation.ts: in the patchedres.endandreq.end, only capturechunkwhentypeof chunk !== "function". The argument is still passed through to the originalend()unchanged, which already handlesend(cb).src/instrumentation/libraries/nextjs/Instrumentation.ts: same guard on the patchedactualRes.end.
Tests
src/instrumentation/libraries/http/integration-tests/end-callback.test.ts(new, follows the existing in-memory-adapter integration pattern; requireshttpafterTuskDrift.initialize()likesmall.test.tsso the RITM hook patches it):- inbound: handler does
res.write(json); res.end(() => {}); asserts the SERVER span'soutputValue.bodydecodes to the written JSON. Fails on main withoutputValue.body === undefined. - outbound: inside an inbound span, an
http.requestis ended withreq.end(() => {}); asserts the upstream call completes (200) and the CLIENT span'sinputValue.bodydecodes to the request body. Fails on main with theERR_INVALID_ARG_TYPE ... Received functionerror surfacing as a 500.
- inbound: handler does
- All
src/instrumentation/libraries/http/**/*.test.tspass (171 tests);tsc --noEmitandnpm run buildpass.
Not changed
res.write/req.writepatches are untouched:write()has no callback-only form.- The Next.js guard is the same one-line change but is not exercised by a runtime test here (needs a Next.js app); it is covered by typecheck and build.
▚▚ Shipped by breken — self-healing software. This one's on us. breken.ai