Fix `PYTHONSTARTUP` exception handling in the REPL
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:
- 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.
- 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
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.
stdlib interpreter-core docs topic-repl 3.13 3.14 3.15
We try not to mix
stdlib,interpreter-core, anddocs. Let's pick one.
Actually... extension-modules. 😅
This is essentially about pymain_run_startup.
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.
@ZeroIntensity topic-repl also
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.
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.
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?
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?
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
>>>
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!
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.
We definitely want users to be able to interrupt a startup file.
Well, the startup file execution is interrupted, but the REPL starts anyway.