#3001·nock

nock 14 hangs on requests that send Connection: keep-alive

Author: wagenetCreated Aug 11, 2026Updated Sep 13, 2026

Please avoid duplicates

I searched open issues; #2789 is related but distinct (that one is about replyWithError, see the note at the end).

What happened?

On [email protected], any intercepted request that sends Connection: keep-alive hangs forever. The mocked response is written without Content-Length and without Transfer-Encoding, so it is only terminated by the socket closing — which never happens on a kept-alive connection.

This is fixed in @mswjs/[email protected], which [email protected] already depends on. [email protected] pins ^0.41.0, so the 14 line still hangs.

Reproducible test case

No nock APIs beyond reply() involved — this is plain https.request:

javascript
const https = require('node:https')
const nock = require('nock')

nock('https://example.test').get('/ka').reply(200, 'hello')

https
  .request(
    { protocol: 'https:', host: 'example.test', path: '/ka', headers: { connection: 'keep-alive' } },
    (res) => {
      let body = ''
      res.on('data', (c) => (body += c))
      res.on('end', () => console.log('done', res.statusCode, body))
    }
  )
  .on('error', console.error)
  .end()

// connection: keep-alive -> no output, hangs
// connection: close      -> "done 200 hello"

Results:

nock connection: close connection: keep-alive
14.0.0-beta.5 ok ok
14.0.0 ok hangs
14.0.17 ok hangs
15.0.0-beta.14 ok ok

So it arrived with the move to @mswjs/interceptors in 14.0.0 stable and is already resolved on the 15 line.

Root cause

MockHttpSocket.respondWith() builds the response via new ServerResponse(new IncomingMessage(socket)). A bare IncomingMessage reports no HTTP version, so Node's ServerResponse constructor takes the HTTP/1.0 path and sets useChunkedEncodingByDefault = false. The serialized response is then:

HTTP/1.1 200 OK\r\n\r\nhello

with no framing headers at all. MockHttpSocket only pushes null when !this.shouldKeepAlive, so on a keep-alive connection there is no terminator and the client waits for the rest of the message indefinitely.

Adding a content-length to the mock's reply headers works around it:

javascript
nock('https://example.test').get('/ka').reply(200, 'hello', { 'content-length': '5' }) // ok

@mswjs/[email protected] reworked the socket layer and no longer has this problem.

Ask

Would you consider bumping @mswjs/interceptors to ^0.42 on the 14 line? Users on nock@14 currently hang on any client that sets Connection: keep-alive explicitly. In our case that is a PowerBI upload client and a docx service client, both of which set the header in application code, so there is nothing to change test-side — we are carrying a local patch of @mswjs/interceptors instead.

I appreciate 0.42 was an API-breaking release for the interceptors integration ([email protected] with 0.42 forced through throws clientRequestInterceptor.dispose is not a function from lib/recorder.js), so this may not be a small backport. Happy to help if you'd like it attempted — and equally happy to be told 15 is the answer and to wait.

Unrelated note on #2789

While investigating I found #2789 (replyWithError with an object) is marked resolved in 15.0.0-beta.8, but it still reproduces on 15.0.0-beta.14:

javascript
nock('https://example.test').get('/x').replyWithError({ message: 'boom' }) // hangs
nock('https://example.test').get('/x').replyWithError(new Error('boom'))   // errors correctly

The reason is dropped upstream in @mswjs/interceptors, which ignores any controller.errorWith() reason that is not an Error instance (mswjs/interceptors#625, still open). I have a fix ready to propose there. Flagging in case #2789 should be reopened.

Nock Version

14.0.17 (also verified against 14.0.0, 14.0.5, 14.0.10 and 15.0.0-beta.14)

Node Version

v20.20.2 and v22

Would you be interested in contributing a fix?

Yes.


Investigated and written by Claude (Anthropic), posting from @wagenet's account.