Repository navigation
Description
Bug report
Bug description
Hosts that call sys.__interactivehook__ to enable history and tab completion, then start a REPL through code.interact() (or similar non-pyrepl input handler) will crash when the user suspends the process (ctrl-z / SIGTSTP) and resumes it (fg / SIGCONT), on Python ≥ 3.14:
Traceback (most recent call last):
File "<string>", line 1, in <module>
import code, sys; sys.__interactivehook__(); code.interact()
File "cpython/Lib/code.py", line 382, in interact
console.interact(banner, exitmsg)
File "cpython/Lib/code.py", line 271, in interact
line = self.raw_input(prompt)
File "cpython/Lib/code.py", line 340, in raw_input
return input(prompt)
File "cpython/Lib/_pyrepl/unix_console.py", line 273, in _sigcont_handler
self.restore()
File "cpython/Lib/_pyrepl/unix_console.py", line 518, in restore
self.__write(ANSIColors.RESET)
File "cpython/Lib/_pyrepl/unix_console.py", line 954, in __write
self.__buffer.append((text, False))
AttributeError: 'UnixConsole' object has no attribute '_UnixConsole__buffer'. Did you mean '._UnixConsole__move' instead of '._UnixConsole__buffer'?
A second symptom (since 3.13, same root cause): history typed into a code.interact() shell is never saved, and existing history in ~/.python_history is not accessible.
With PYTHON_BASIC_REPL=1 both symptoms disappear.
Reproduction
In a pty/terminal:
python -c 'import code, sys; sys.__interactivehook__(); code.interact()'
[ctrl-Z to suspend to shell]
fg
-> resumes with "now exiting InteractiveConsole..." followed by traceback above
I discovered this in Django's shell, which uses this exact mechanism, and I imagine other REPL tools do as well. I believe this is a cpython issue because as far as I can tell code.interact() can't get the full benefits of interactive mode when pyrepl is enabled.
I have reproduced this on 3.14.9, 3.15.2, and main. I have a fix for the crash itself but the history issue is still outstanding.
Root cause
site.register_readline() (installed as sys.__interactivehook__) performs a half-install of pyrepl when CAN_USE_PYREPL:
- To load history, it calls
_pyrepl.readline.read_history_file(), which goes through the module-level_ReadlineWrappersingleton — lazily constructing the process's onlyUnixConsole(_ReadlineWrapper.get_reader()). UnixConsole.__init__ends withsignal.signal(signal.SIGCONT, self._sigcont_handler). The handler callsself.restore(), thenself.prepare().- All the state the handler touches —
__buffer,__svtermstate, the rendered screen — is created only inprepare(), which is called only fromreader.readline(). - A
code.interact()host never drives that reader:code.pyimports the C readline module forbuiltins.input(), and_pyrepl.readline._setup()— the only placebuiltins.inputis monkeypatched to pyrepl — never runs. So the hook-created console stays armed-but-never-prepared for the whole session. SIGCONTthen fires the dormant console's handler →restore()→AttributeErroron__buffer. An unhandled exception inside a signal handler duringinput()kills the shell.
The history-loss symptom is the same half-install acting through the C module: the hook loads history into the pyrepl reader while code.interact()'s input goes through C readline/libedit, and the atexit hook in site.register_readline() writes the pyrepl reader's (empty) history list back over the file.
The official REPL is unaffected because its console is driven through pyrepl's own reader.readline().
AI disclosure
I used an AI assistant to help find and explain the root cause.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Activity