You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
teeny-request: the timeout option is ignored since 10.0.0 (node-fetch 3 has no timeout option) #9514
requestToFetchOptions copies reqOpts.timeout into the node-fetch options. node-fetch 3 removed the timeout option, so the value is silently ignored, and a request to a server that never responds waits forever.
This is a regression from teeny-request 10.0.0, which moved from node-fetch 2 to node-fetch 3. With teeny-request 9.0.0 (node-fetch 2.7.0), the same request fails after the timeout.
It affects every library that passes timeout to teeny-request. For example, @google-cloud/storage sets timeout: 60000 as a default for all requests (nodejs-common/util.js). Since storage 8.0.1, which depends on teeny-request ^11, that timeout has no effect.
Reproduction
consthttp=require('node:http')const{ teenyRequest }=require('teeny-request')// The server accepts the request and never sends response headers.constserver=http.createServer(()=>{})server.listen(0,()=>{consturl=`http://127.0.0.1:${server.address().port}/`conststartedAt=Date.now()teenyRequest({uri: url,timeout: 1000},(err)=>{console.log(`callback after ${Date.now()-startedAt} ms:`,err?.message)process.exit(0)})setTimeout(()=>{console.log(`no callback after ${Date.now()-startedAt} ms`)process.exit(1)},5000)})
Output with teeny-request 11.0.1 (node-fetch 3.3.2):
no callback after 5010 ms
Output with teeny-request 9.0.0 (node-fetch 2.7.0):
callback after 1003 ms: network timeout at: http://127.0.0.1:50922/
Expected behavior
The request fails after about 1000 ms, as it does with teeny-request 9.x.
One possible fix is to pass signal: AbortSignal.timeout(reqOpts.timeout) to fetch. That limits the total request time, which would cut off long streaming downloads, so streaming requests may need a socket idle timeout on the agent instead. In node-fetch 2, the timeout stopped at the response headers for a streamed body.
Related: #9515 (a stalled @google-cloud/storage download never errors).
teeny-request already has a working http/https implementation with init.timeout + stream-activity reset in src/index.ts:
In PR #9573, bunFetch was added to core/packages/teeny-request/src/index.ts:36-314 using standard Node http.request / https.request APIs. It already enforces init.timeout via req.setTimeout(init.timeout, onTimeout) and resets the timer on every response data chunk (incoming.on('data', resetReqTimeout) at lines 197–200, which also addresses storage: createReadStream() never errors when a download stalls mid-body #9515). However, lines 311–314 currently only route to bunFetch when 'Bun' in globalThis is true, and otherwise still delegate to import('node-fetch') (node-fetch@3) on Node.js.
Activity
wvanderdeijl commentedon Oct 23, 2026
Two related notes while investigating
@google-cloud/storagetimeout behavior:Regarding the
@google-cloud/storagetimeout: 60000(nodejs-common/util.js) example in the issue description:Even on
@google-cloud/storage@7.x(withteeny-request@9/node-fetch@2) — and even once teeny-request: the timeout option is ignored since 10.0.0 (node-fetch 3 has no timeout option) #9514 is fixed inteeny-request— that60000ms default inrequestDefaultsis currently bypassed unless the user explicitly passesnew Storage({ timeout }). Inhandwritten/storage/src/nodejs-common/service.ts:222,reqOpts = {...reqOpts, timeout: this.timeout}sets an explicit{ timeout: undefined }property whenthis.timeoutisundefined, which overwrites bothrequestDefaults.timeout(60000) inteenyRequest.defaults(...)({...defaults, ...reqOpts}) and any per-callreqOpts.timeoutpassed viafile.createWriteStream({ resumable: false, timeout })orbucket.upload(path, { resumable: false, timeout }). I filed fix(storage): Service.request_ sets { timeout: undefined }, clobbering the 60s requestDefaults.timeout and per-call options.timeout #9708 to track that specific bug inService.prototype.request_.teeny-requestalready has a workinghttp/httpsimplementation withinit.timeout+ stream-activity reset insrc/index.ts:In PR #9573,
bunFetchwas added tocore/packages/teeny-request/src/index.ts:36-314using standard Nodehttp.request/https.requestAPIs. It already enforcesinit.timeoutviareq.setTimeout(init.timeout, onTimeout)and resets the timer on every response data chunk (incoming.on('data', resetReqTimeout)at lines 197–200, which also addresses storage: createReadStream() never errors when a download stalls mid-body #9515). However, lines 311–314 currently only route tobunFetchwhen'Bun' in globalThisis true, and otherwise still delegate toimport('node-fetch')(node-fetch@3) on Node.js.