cpython icon indicating copy to clipboard operation
cpython copied to clipboard

Fix `PYTHONSTARTUP` exception handling in the REPL

Open johnslavik opened this issue 2 months ago • 11 comments

Bug report

Bug description:

EDIT: Some exceptions currently do not prevent the REPL from starting but should -- that's the bug. Below is a report about SystemExit and request for clarity if it was the intended behavior for it to be able to prevent the REPL from starting. It seems that SystemExit should prevent the REPL from starting, but there are other exceptions such as KeyboardInterrupt that do stop the Python startup script execution, but still proceed to a new REPL session, and that likely isn't desired.


To reproduce:

  1. The new REPL
❯ PYTHONSTARTUP=<(echo 'raise SystemExit') ./python
Python 3.15.0a3+ (heads/main:5b5263648f2, Dec 20 2025, 07:19:41) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
  1. The basic REPL
❯ PYTHON_BASIC_REPL=1 PYTHONSTARTUP=<(echo 'raise SystemExit') ./python
Python 3.15.0a3+ (heads/main:5b5263648f2, Dec 20 2025, 07:19:41) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.

(both actually hit the same code path -- Py_RunMain -> pymain_run_python -> pymain_run_stdin -> pymain_run_startup -> _PyRun_SimpleFileObject -> PyErr_Print -> PyErr_PrintEx -> _PyErr_PrintEx -> handle_system_exit)

While this is largely an edge case of the regular REPLs, copying this semantics just feels wrong in Python code. I've run into this while adding PYTHONSTARTUP exception handling to the asyncio REPL in GH-140288.

Should I be keeping asyncio REPL in parity with Python-side REPLs behavior in this regard? Do we want to fix this in the C code?

And lastly, as part of this issue we would document the exception handling of PYTHONSTARTUP.

@ZeroIntensity stdlib interpreter-core docs topic-repl 3.13 3.14 3.15

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

johnslavik avatar Dec 20 '25 17:12 johnslavik

stdlib interpreter-core docs topic-repl 3.13 3.14 3.15

We try not to mix stdlib, interpreter-core, and docs. Let's pick one.

ZeroIntensity avatar Dec 20 '25 17:12 ZeroIntensity

stdlib interpreter-core docs topic-repl 3.13 3.14 3.15

We try not to mix stdlib, interpreter-core, and docs. Let's pick one.

Actually... extension-modules. 😅 This is essentially about pymain_run_startup.

johnslavik avatar Dec 20 '25 17:12 johnslavik

I'm very unsure about the likely user expectation here, but I'm leaning toward thinking that the REPL should always open despite any PYTHONSTARTUP failures.

johnslavik avatar Dec 20 '25 18:12 johnslavik

@ZeroIntensity topic-repl also

johnslavik avatar Dec 20 '25 18:12 johnslavik

Should I be keeping asyncio REPL in parity with Python-side REPLs behavior in this regard?

I think so. It'd be surprising if this weren't the case.


If someone raises SystemExit in PYTHONSTARTUP, something really went wrong. We should not start the REPL. For instance, this could mean that a main() function got executed somewhere and that's likely by mistake. The REPL also exits with argparse when you parse arguments that fail to be parsed and we have an option to disable this: https://docs.python.org/3/library/argparse.html#exit-on-error.

So the REPL should exit when encountering a SystemExit. You'll also have other inconsistencies:

  • Start REPL with PYTHONSTARTUP being bad but ignore it.
  • Reload startup -> this won't be ignored this time.

picnixz avatar Jan 04 '26 15:01 picnixz

For that reason, I'm converting this into a feature request. But I honestly think we should NOT start the REPL if the starting script failed. Or make sure that we have an option for that.

picnixz avatar Jan 04 '26 15:01 picnixz

Thanks! That's helpful. I didn't have a strong intuition here, and I fully agree with your rationale.

I suggest we just convert this to docs and I'll simply document the current behavior to close.

SystemExit is somewhat special, because no other exception (even a BaseException) can prevent the REPL from starting.

As the OP, I'm not in strong favor of an extra option, so maybe let's leave it for others to open a feature request if they have a good use case to have an opt-in for changing the existing behavior.

WDYT?

johnslavik avatar Jan 04 '26 16:01 johnslavik

I think it makes sense to indicate that SystemExit is propagated.

SystemExit is somewhat special, because no other exception (even a BaseException) can prevent the REPL from starting.

This part is a bug IMO. If I raise a KeyboardInterrupt during startup (e.g., someone may have a startup that spawns processes!!), I want to interrupt the entire Python process. Does it work or not?

picnixz avatar Jan 04 '26 16:01 picnixz

If I raise a KeyboardInterrupt during startup (e.g., someone may have a startup that spawns processes!!), I want to interrupt the entire Python process. Does it work or not?

Nope not really

cpython on  asyncio-repl-handle-python-startup ~ cpython 
❯ PYTHONSTARTUP=t.py ./python -q
^CTraceback (most recent call last):
  File "t.py", line 2, in <module>
    time.sleep(100)
    ~~~~~~~~~~^^^^^
KeyboardInterrupt
>>> 

johnslavik avatar Jan 04 '26 16:01 johnslavik

I think this is a bug here. We definitely want users to be able to interrupt a startup file. What if I messed up my startup with some infinite loop for instance!

picnixz avatar Jan 04 '26 16:01 picnixz

Yup, 100%.

I guess we'd want to add more intentional semantics to exceptions in the REPL startup.

A simple approach would be to abort starting the REPL on an uncaught BaseException but proceed on an uncaught Exception. Display the traceback at all times. Plus if the REPL cannot start due to a BaseException, show a message like "Could not start the interactive REPL because of an uncaught exception in the startup script $PYTHONSTARTUP". And perhaps if we proceed with an uncaught Exception, show a message akin to threading's "Ignoring exception in the startup script $PYTHONSTARTUP" then traceback.

johnslavik avatar Jan 04 '26 16:01 johnslavik

We definitely want users to be able to interrupt a startup file.

Well, the startup file execution is interrupted, but the REPL starts anyway.

johnslavik avatar Jan 07 '26 01:01 johnslavik