Skip to content

readline Regression since v24.2.0 Cannot read properties of undefined (reading 'pause') #61526

@robhogan

Description

@robhogan

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.js

How 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:

this.setupHistoryManager(input);

Note that's before this.input is assigned, here:

this.input = input;

Which is a problem because setupHistoryManager may call initialize()

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)

this[kContext].pause();

CC @puskin

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions