-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
Description
Version
v24.13.0
Platform
Darwin robhogan-mac 25.1.0 Darwin Kernel Version 25.1.0: Mon Oct 20 19:34:05 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6041 arm64
Subsystem
readline
What steps will reproduce the bug?
// readline-repro.js
const readline = require('readline');
const {Readable} = require('stream');
const input = Readable.from([]);
input.onHistoryFileLoaded = () => 'oops';
readline.createInterface(input);node ./readline-repro.jsHow often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
Prior to Node v24.2.0, this did not crash
What do you see instead?
node readline-repro.js
node:internal/readline/interface:571
this.input.pause();
^
TypeError: Cannot read properties of undefined (reading 'pause')
at Interface.pause (node:internal/readline/interface:571:16)
at ReplHistory.initialize (node:internal/repl/history:111:20)
at Interface.setupHistoryManager (node:internal/readline/interface:392:27)
at Interface.InterfaceConstructor (node:internal/readline/interface:218:8)
at new Interface (node:readline:115:3)
at Object.createInterface (node:readline:212:10)
at Object.<anonymous> (/private/tmp/readline-repro.js:6:21)
at Module._compile (node:internal/modules/cjs/loader:1734:14)
at Object..js (node:internal/modules/cjs/loader:1899:10)
at Module.load (node:internal/modules/cjs/loader:1469:32)
Node.js v24.2.0
Additional information
The example is contrived (what are we doing assigning onHistoryFileLoaded anyway?) but we hit this in the wild in a Node.js update when passing jest.mock() as the input. Rightly or wrongly, jest-mock by default uses a proxy object to create properties as mock functions on access.
So a more real-world issue would be:
// readline-test.js
const readline = require('readline');
readline.createInterface(jest.mock());Now crashes.
I root caused this to #58225. Repeating here:
setupHistoryManager is called within InterfaceConstructor, here:
node/lib/internal/readline/interface.js
Line 218 in 644ba1f
| this.setupHistoryManager(input); |
Note that's before this.input is assigned, here:
node/lib/internal/readline/interface.js
Line 236 in 644ba1f
| this.input = input; |
Which is a problem because setupHistoryManager may call initialize()
node/lib/internal/readline/interface.js
Lines 384 to 389 in 644ba1f
| setupHistoryManager(options) { | |
| this.historyManager = new ReplHistory(this, options); | |
| if (options.onHistoryFileLoaded) { | |
| this.historyManager.initialize(options.onHistoryFileLoaded); | |
| } |
Which tries to call .pause() on the interface's input property (still before it has been assigned)
node/lib/internal/repl/history.js
Line 111 in 644ba1f
| this[kContext].pause(); |
CC @puskin