Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    readlineIssues and PRs related to the built-in readline module.staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions