Skip to content

asyncio.create_subprocess_shell does not consistently result in negative returncode on signal termination #138234

Description

@danielrainer

Bug report

Bug description:

The docs for returncode state the following:

A negative value -N indicates that the child was terminated by signal N (POSIX only).

which does not technically require, but strongly suggest that processes terminated by signals will have a negative returncode corresponding to the signal. On some platforms (observed on Arch Linux and macOS, both using Python 3.13.7), this does indeed seem to be the case. On others it is not. The observed counterexample is Ubuntu using Python 3.12.3. There, returncode seems to be set to 128 + signal value. Technically, this does not violate the documented behavior, but it is very surprising. If that is intended behavior, the docs deserve a clear warning. My preference would be to mandate negative returncodes upon termination by signal in the docs and to adapt the code on non-conformant platforms accordingly.

Interestingly, asyncio.create_subprocess_exec has the expected behavior of consistently negative returncodes on the tested platforms, which increases my suspicion that there is indeed undesired behavior involved.

import asyncio


async def repro():
    proc = await asyncio.create_subprocess_shell("./sigint.sh")
    await proc.communicate()
    print(proc.returncode)
    assert proc.returncode == -2


asyncio.run(repro())

sigint.sh:

#!/bin/sh
kill -2 $$

I created a repo to show the differences in GitHub's CI: https://github.com/danielrainer/python_signal_returncode_repro/actions/runs/17310888203

CPython versions tested on:

3.13, 3.12

Operating systems tested on:

Linux, macOS

Linked PRs

Activity

  1. andreuu-tsai commented on Sep 5, 2025

    @andreuu-tsai
    Contributor

    When using create_subprocess_shell, proc.returncode is 130 after sending SIGINT.
    When using create_subprocess_exec, the return code is -2 as expected (terminated by SIGINT).

    The current documentation says:

    A negative value -N indicates that the child was terminated by signal N (POSIX only).

    This is accurate for create_subprocess_exec, where the child process is executed directly.
    But for create_subprocess_shell, the child is wrapped by /bin/sh, and the observed return code comes from the shell’s exit status rules (e.g. 128+N for termination by signal N).
    So in this case, the return code follows Bash Exit Status
    (or the corresponding shell), not the -N convention.

    Should the documentation clarify this difference between create_subprocess_exec (direct POSIX behavior) and create_subprocess_shell (shell exit status)?

  2. danielrainer commented on Sep 9, 2025

    @danielrainer
    Author

    Should the documentation clarify this difference between create_subprocess_exec (direct POSIX behavior) and create_subprocess_shell (shell exit status)?

    I think the documentation should accurately describe the behavior of the functions, that's the point of having documentation.

    Regarding the behavior of create_subprocess_shell, I get that forwarding the exit status of the shell is the simplest way of implementing returncode. Unfortunately, that makes it inconvenient for users of the interface to do anything useful with the returncode, since they would have to know about all possible shells which might be used and their respective conventions. It might not be realistic, or even desirable, to change the implementation to have consistent behavior on all (POSIX) platforms. If the current behavior is kept, there should be a clear indication that the shell's exit code is used, and that this code depends on which shell is used.

    Regardless of what is done wrt create_subprocess_shell, the documentation create_subprocess_exec should be clarified to state whether signal termination always results in a negative returncode indicating which signal caused termination. At the moment, an implementation which sometimes returns negative values as described when a signal terminates the process, but other times returns non-negative values for signal termination is allowed by the docs. I don't think this should ever happen, but from reading the docs I don't know if it could. If it actually can happen, it certainly should to be noted in the documentation.

  3. added a commit that references this issue on Mar 21, 2026
  4. added 2 commits that reference this issue on Mar 21, 2026
  5. moved this from Todo to Done in asyncioon Mar 21, 2026
  6. added 2 commits that reference this issue on Mar 21, 2026
  7. added a commit that references this issue on Apr 25, 2026
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

    stdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions