Coverage Ledger — Python: Language Semantics, Standard Library, Process APIs, and Subprocesses (G4 schema)
Chapter: ./python.md — Section G, process runtime. This ledger inventories every documented API and behavior claim in the chapter as one row each, ordered by chapter section (§1 syntax/semantics behavior rows first, then §2–§6 API/behavior rows in order of first appearance).
Scope. CPython 3.15.0, the current stable release per python.org/downloads (“Download Python
3.15.0”; the previous line 3.14.8 confirmed at docs.python.org/3.14/). Every signature was verified
verbatim against the docs.python.org/3 library/reference pages as served at retrieval (2026-10-13;
docs.python.org/3 serves the 3.15.0 documentation). CPython-specific behavioral claims are anchored to
those pages and, where a numeric fact exists only in the implementation, to the CPython source tree
(github.com/python/cpython main: Python/pylifecycle.c, Lib/subprocess.py, Lib/threading.py).
PEPs 475/446/442 (peps.python.org) are cited as normative references for EINTR retry, fd inheritance,
and finalization. Platform layer: Linux primary; POSIX availability markers as documented; Windows
deltas labeled per row. No source was unreachable at retrieval time — there are no source-limited
rows.
Status definitions.
documented— the claim is documented in./python.mdand anchored to a fetched authoritative source (docs.python.org page, CPython source file, or PEP) exactly as the chapter cites it.source-limited— documented but primary wording unreachable at retrieval time. Unused here (Remaining: 0).not-started/researched— defined by the repo ledger convention; unused in this ledger.
Rollup: Inventoried: 80 / Documented: 80 / Remaining: 0 / Source-limited: 0
Section layout: §1 → PL-PY-001–012 · §2 → PL-PY-013–031 · §3 → PL-PY-032–040 · §4 → PL-PY-041–049 · §5 → PL-PY-050–071 · §6 → PL-PY-072–080.
| ID | Language/API or behavior | Canonical signature | Documentation path | Authoritative reference | Version/platform scope | Status |
|---|---|---|---|---|---|---|
| PL-PY-001 | §1.1 Name→object binding model; assignment rebinds, never copies; fully specified evaluation order: left to right, RHS of assignment before LHS; short-circuit and/or/conditional are the only operand-skipping forms (no unsequenced-evaluation UB class) | — | ./python.md | Python reference, Expressions §6.16 “Evaluation order” + §4.2.1 Binding of names, docs.python.org/3/reference/expressions.html, /3/reference/executionmodel.html (3.15.0 docs) | Python language (all 3.x) | documented |
| PL-PY-002 | §1.2 Scope resolution: nearest enclosing scope (local → enclosing functions → global → builtins); NameError/UnboundLocalError semantics; block-static binding determined by scanning the block; global/nonlocal; runtime resolution of free variables | — | ./python.md | Python reference, Execution model §4.2.2 “Resolution of names”, docs.python.org/3/reference/executionmodel.html | Python language (all 3.x) | documented |
| PL-PY-003 | §1.3 Object lifetime: CPython deterministic refcount destruction + generational cycle GC; PEP 442 (3.4+) safe finalization of cycles, __del__ at most once; language guarantee limited: no __del__ timing or shutdown-invocation guarantee; gc.garbage empty-by-default era | — | ./python.md | Data model §3.1.13 object.__del__, docs.python.org/3/reference/datamodel.html; PEP 442 (peps.python.org); gc module docs | [language] weak guarantee + [CPython] refcounting/cycle GC; PEP 442 → 3.4+ | documented |
| PL-PY-004 | §1.4 BaseException vs Exception split: SystemExit, KeyboardInterrupt, GeneratorExit inherit BaseException so broad except Exception: cannot swallow exit/interrupt requests | — | ./python.md | Exceptions docs (BaseException hierarchy; SystemExit, KeyboardInterrupt), docs.python.org/3/library/exceptions.html | Python language (all 3.x) | documented |
| PL-PY-005 | §1.5 try/except/else/finally: finally runs on every exit path incl. SystemExit; exception/return in finally replaces the in-flight exception (chained via __context__); with calls __exit__ on every path, return-true suppresses | — | ./python.md | Python reference (try statement) + Exceptions docs + Data model (context managers), docs.python.org/3 | Python language (all 3.x) | documented |
| PL-PY-006 | §1.6 Exception chaining: implicit __context__ set when raising during handling; raise X from Y sets __cause__ + __suppress_context__; default traceback renders both chains; __traceback__ carried on every exception | BaseException.__context__ / BaseException.__cause__ / BaseException.__suppress_context__ / BaseException.__traceback__ | ./python.md | Exceptions docs, docs.python.org/3/library/exceptions.html (exception chaining section) | Python language (3.0+ chaining) | documented |
| PL-PY-007 | §1.7 Generator cleanup: close() raises GeneratorExit at the suspension point; finally in the generator runs; yielding again after GeneratorExit → RuntimeError; aclose() async analogue; unawaited-coroutine warning | generator.close() / coroutine.aclose() | ./python.md | Data model (coroutine/generator close semantics), docs.python.org/3/reference/datamodel.html | Python language (2.5+ generators; 3.5+ async) | documented |
| PL-PY-008 | §1.8 Normal vs exceptional termination vocabulary: script end → shutdown/status 0; sys.exit → shutdown/status=arg; uncaught exception → traceback + status 1; uncaught SystemExit → no traceback; os._exit/os.abort/external signal → no Python shutdown | — | ./python.md | Data model + sys/exceptions docs (this chapter’s §1.8 table) | Python language (all 3.x) | documented |
| PL-PY-009 | §1.9 In-scope unspecified areas: __del__ timing; no defined memory model (GIL is implementation); string interning unspecified except sys.intern(); handler-vs-bytecode interleaving unspecified; shutdown destruction order beyond the underscore rule | — | ./python.md | Python reference (data model, execution model) read as specification-silence; chapter §1.9 | Python language (all 3.x); specifics [CPython] | documented |
| PL-PY-010 | §1.10 Version deltas 3.9→3.15 in scope: 3.9 group/user/extra_groups/umask; 3.11 process_group + exception groups; 3.12 threaded-fork DeprecationWarning + subinterpreter RuntimeError; 3.15 event-driven Popen.wait timeout (Linux ≥ 5.3 pidfd, macOS/BSD kqueue); free-threaded build makes delivery/interleaving claims build-dependent | — | ./python.md | docs.python.org/3 library versionchanged/versionadded notes (subprocess, os, signal, threading) | CPython 3.9 → 3.15.0 | documented |
| PL-PY-011 | §1.11 Misconceptions corrected: sys.exit is catchable (SystemExit); GC is not deterministic for cycles/__del__; finally runs for SystemExit but not under os._exit and not in killed daemon threads; shell=False passes metacharacters safely; negative returncode = killed-by-signal | — | ./python.md | sys/exceptions/subprocess/threading docs (docs.python.org/3) | Python language + CPython (all in-scope versions) | documented |
| PL-PY-012 | §1.5 worked example: Guard.__exit__ runs on sys.exit(0) (prints enter/exit), SystemExit not suppressed with return False → status 0 | __exit__(self, exc_type, exc, tb) -> bool | ./python.md | Data model (with statement context managers), docs.python.org/3/reference/datamodel.html | Python language (2.6+ with) | documented |
| PL-PY-013 | sys.exit: raises SystemExit to signal intent; int arg → status (0 = success); None → 0; other object → printed to stderr + status 1; cleanup honored; catchable | sys.exit(arg=0) | ./python.md | sys docs, docs.python.org/3/library/sys.html#sys.exit (3.15.0, retrieved 2026-10-13) | Python 3.x, all platforms | documented |
| PL-PY-014 | SystemExit: inherits BaseException not Exception; unhandled → interpreter exits, no traceback; constructor arg = sys.exit arg; .code attribute | SystemExit(code=0) (exception class) | ./python.md | Exceptions docs, docs.python.org/3/library/exceptions.html#SystemExit | Python 3.x, all platforms | documented |
| PL-PY-015 | os._exit: exit process with status n without cleanup handlers, stdio flush, etc.; docs: normally only used in child after fork() | os._exit(n, /) | ./python.md | os docs, docs.python.org/3/library/os.html#os._exit | Python 3.x, all platforms (POSIX semantics per OS) | documented |
| PL-PY-016 | os.abort: generates SIGABRT to current process; Unix default → core dump, Windows → exit code 3; does NOT call a Python SIGABRT handler; bypasses Python shutdown | os.abort() | ./python.md | os docs, docs.python.org/3/library/os.html#os.abort | Python 3.x; Unix core-dump / Windows exit-3 delta | documented |
| PL-PY-017 | atexit.register: handler runs at interpreter shutdown in reverse (LIFO) order; same func registerable repeatedly; returns func (decorator) | atexit.register(func, *args, **kwargs) | ./python.md | atexit docs, docs.python.org/3/library/atexit.html#atexit.register | Python 3.x, all platforms | documented |
| PL-PY-018 | atexit.unregister: removes all ==-equal registrations; silent no-op if absent; registering/unregistering from within a running cleanup function is documented undefined | atexit.unregister(func) | ./python.md | atexit docs, docs.python.org/3/library/atexit.html#atexit.unregister | Python 3.x, all platforms | documented |
| PL-PY-019 | signal.signal: set handler (2-arg callable or SIG_IGN/SIG_DFL), returns previous; main-thread-of-main-interpreter only (else ValueError); Windows limited to SIGABRT/SIGFPE/SIGILL/SIGINT/SIGSEGV/SIGTERM/SIGBREAK | signal.signal(signalnum, handler) | ./python.md | signal docs, docs.python.org/3/library/signal.html#signal.signal | Python 3.x; Windows signal set delta | documented |
| PL-PY-020 | signal.getsignal: return current handler — callable, SIG_IGN, SIG_DFL, or None (not installed from Python) | signal.getsignal(signalnum) | ./python.md | signal docs, docs.python.org/3/library/signal.html#signal.getsignal | Python 3.x, all platforms | documented |
| PL-PY-021 | signal.set_wakeup_fd: writes signal number as one byte on each caught signal; fd must be non-blocking; -1 disables; returns old fd; main-thread-only; warn_on_full_buffer=True (3.8+) warns on dropped bytes | signal.set_wakeup_fd(fd, *, warn_on_full_buffer=True) | ./python.md | signal docs, docs.python.org/3/library/signal.html#signal.set_wakeup_fd | Python 3.x; warn_on_full_buffer 3.8+ | documented |
| PL-PY-022 | signal.pthread_sigmask: fetch/change calling thread’s signal mask (SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK), returns old mask set; Availability: Unix | signal.pthread_sigmask(how, mask) | ./python.md | signal docs, docs.python.org/3/library/signal.html#signal.pthread_sigmask | Python 3.3+; Unix | documented |
| PL-PY-023 | signal.sigpending: set of signals pending for delivery to calling thread; Availability: Unix | signal.sigpending() | ./python.md | signal docs, docs.python.org/3/library/signal.html#signal.sigpending | Python 3.3+; Unix | documented |
| PL-PY-024 | signal.sigwait: suspend calling thread until one of sigset is deliverable; accepts (removes from pending) and returns signal number; Availability: Unix | signal.sigwait(sigset) | ./python.md | signal docs, docs.python.org/3/library/signal.html#signal.sigwait | Python 3.3+; Unix | documented |
| PL-PY-025 | signal.raise_signal (send signal to calling process, 3.8+) and signal.valid_signals (set of valid signal numbers, 3.8+); SIG_DFL/SIG_IGN constants | signal.raise_signal(signum) / signal.valid_signals() | ./python.md | signal docs, docs.python.org/3/library/signal.html#signal.raise_signal, #signal.valid_signals | Python 3.8+ | documented |
| PL-PY-026 | subprocess.Popen full constructor surface (verified verbatim incl. 3.11 process_group, 3.10 pipesize); execvpe-like on POSIX, CreateProcess on Windows; no env_merge parameter exists — env is a complete mapping | subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, extra_groups=None, user=None, umask=-1, encoding=None, errors=None, text=None, pipesize=-1, process_group=None) | ./python.md | subprocess docs, docs.python.org/3/library/subprocess.html#subprocess.Popen | CPython 3.11+/3.15.0 surface; POSIX/Windows split as documented | documented |
| PL-PY-027 | subprocess stdio constants: PIPE, DEVNULL, STDOUT (stderr→stdout merge) | subprocess.PIPE / subprocess.DEVNULL / subprocess.STDOUT | ./python.md | subprocess docs, docs.python.org/3/library/subprocess.html#subprocess.PIPE | Python 3.3+ (DEVNULL) | documented |
| PL-PY-028 | subprocess.run: high-level one-shot → CompletedProcess; timeout/input/check/capture_output not passed through; timeout → child killed and waited for, then TimeoutExpired re-raised; process creation itself not interruptible on many platforms | subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None, **other_popen_kwargs) -> CompletedProcess | ./python.md | subprocess docs, docs.python.org/3/library/subprocess.html#subprocess.run | Python 3.5+; capture_output 3.7+ | documented |
| PL-PY-029 | CompletedProcess: run()’s result record (.args/.returncode/.stdout/.stderr; .check_returncode()); negative returncode = killed by signal N (POSIX only) | CompletedProcess(args, returncode, stdout=None, stderr=None) | ./python.md | subprocess docs, docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess | Python 3.5+ | documented |
| PL-PY-030 | Legacy trio call / check_call / check_output: return returncode / raise CalledProcessError on non-zero / capture stdout and raise with .output; docs direct users to run() | subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs); check_call(...same...); check_output(args, *, stdin=None, stderr=None, shell=False, cwd=None, encoding=None, errors=None, universal_newlines=None, timeout=None, text=None, **other_popen_kwargs) | ./python.md | subprocess docs, docs.python.org/3/library/subprocess.html#subprocess.call / #subprocess.check_call / #subprocess.check_output | Python 3.5+ (wrappers over run) | documented |
| PL-PY-031 | Exception family: SubprocessError base (3.3+); CalledProcessError (.returncode/.cmd/.output), TimeoutExpired (.cmd/.timeout, partial output); invalid Popen args → ValueError | SubprocessError / CalledProcessError / TimeoutExpired (exception classes) | ./python.md | subprocess docs, docs.python.org/3/library/subprocess.html#exceptions | Python 3.3+ | documented |
| PL-PY-032 | §3.2 exit-status derivation: int → C exit() (OS truncates to low 8 bits on POSIX: ≥256 wraps, negatives wrap); docs: most systems require 0–127; non-int → stderr print + 1; None → 0 | — | ./python.md | sys docs (#sys.exit, retrieved 2026-10-13) + POSIX exit() layer; decoder: ../../linux/process-exit-signal/languages/python.md | [language] arg mapping + [POSIX/Linux] status-byte truncation | documented |
| PL-PY-033 | §3.3 SystemExit propagation: runs finally/exit on the way out; except BaseException catches; re-raise resumes exit; swallowing cancels the exit; in non-main thread ends only that thread | — | ./python.md | sys docs (“only exit the process when called from the main thread”) + exceptions docs | Python language (all 3.x) | documented |
| PL-PY-034 | §3.4 Uncaught exception → traceback to stderr + exit status 1; normal shutdown sequence still runs afterwards; KeyboardInterrupt same unless decoded via 128+N from actual signal death | — | ./python.md | exceptions docs + sys docs + chapter §3.4 | Python language + CPython | documented |
| PL-PY-035 | §3.5 os._exit semantics: no atexit, no threading._shutdown, no stdio flush, no module teardown, no __del__; buffered data (Python + C stdio) lost; sanctioned fork-child exit | os._exit(n, /) (behavior row) | ./python.md | os docs + exceptions docs cross-reference | Python 3.x, all platforms | documented |
| PL-PY-036 | §3.6 os.abort semantics: SIGABRT to self bypassing Python handler machinery; shell status 128+6=134 with core/caveat; no shutdown sequence | os.abort() (behavior row) | ./python.md | os docs (#os.abort) + POSIX SIGABRT disposition | Python 3.x; Unix / Windows exit-3 delta | documented |
| PL-PY-037 | §3.7 Interpreter shutdown sequence (CPython, verified in pylifecycle.c): threading._shutdown joins non-daemon threads → pending calls → atexit LIFO (looped until quiescent) → module teardown + final GC/del finalization → stdio flush (failure after SystemExit caught → exit status 120) → C exit(sts); daemon threads not joined; per-interpreter atexit stacks (3.12+) | — | ./python.md | docs.python.org/3/library/sys.html#sys.exit versionchanged 3.6 (status 120); CPython Python/pylifecycle.c (wait_for_thread_shutdown, _PyAtExit_Call, sts = 120); atexit docs (multi-interpreter) | [CPython] 3.15.0 source + 3.6+ documented status-120 rule | documented |
| PL-PY-038 | §3.10 atexit exact behavior: LIFO order (A,B,C → C,B,A); handler exception → traceback printed (unless SystemExit), info saved, all handlers still run, last exception re-raised at the end (→ status 1); registering from a cleanup function undefined | atexit.register(func, *args, **kwargs) (behavior row) | ./python.md | atexit docs, docs.python.org/3/library/atexit.html (retrieved 2026-10-13, verbatim quotes) | Python 3.x, all platforms | documented |
| PL-PY-039 | §3.9 Module teardown: __del__ may run during shutdown when globals (incl. modules) may be deleted or set to None; only guarantee: underscore-prefixed globals deleted first; pre-3.4 all globals were forcibly None’d; exceptions escaping __del__ printed as “Exception ignored in:”, never change exit status | — | ./python.md | Data model §3.1.13 (object.del, docs.python.org/3/reference/datamodel.html) | Python language (3.4+ semantics; CPython teardown) | documented |
| PL-PY-040 | §3.8 thread vs process: sys.exit in a thread raises SystemExit in that thread only (thread machinery swallows it); main thread’s SystemExit → process exit; children (fork/multiprocessing/subprocess) have independent exit statuses reaped by the parent | — | ./python.md | sys docs + threading docs + chapter §3.8 | Python language + CPython | documented |
| PL-PY-041 | §4.1 Signal delivery model: C-level handler only sets a flag; Python handler runs at a later point (e.g. next bytecode) in the main thread; SIGFPE/SIGSEGV catching futile; long C computations delay handlers; handler exceptions raised “out of thin air” in main thread | — | ./python.md | signal docs (“Execution of Python signal handlers”), docs.python.org/3/library/signal.html | [CPython] implementation model, documented; build-dependent on free-threaded | documented |
| PL-PY-042 | §4.2 Defaults and persistence: SIGPIPE ignored (→ BrokenPipeError etc.), SIGINT → KeyboardInterrupt (unless parent changed it); BSD-style persistent handlers regardless of platform; SIGCHLD follows the underlying implementation | — | ./python.md | signal docs (“General rules”), docs.python.org/3/library/signal.html | [CPython] all 3.x; per-OS SIGCHLD delta | documented |
| PL-PY-043 | §4.3 Main-thread-only: handlers always executed in main Python thread of main interpreter (signals unusable for inter-thread communication); signal.signal/set_wakeup_fd raise ValueError off-main; locks must not be acquired inside handlers | — | ./python.md | signal docs (“Signals and threads” + per-function notes) | [CPython] all 3.x | documented |
| PL-PY-044 | §4.4 EINTR / PEP 475: since 3.5, EINTR-failing syscalls are retried automatically (no InterruptedError leakage); time.sleep sleeps at least the requested duration across signal interruptions unless the handler raises; pre-3.5 catch-InterruptedError obsolete | — | ./python.md | PEP 475 (peps.python.org/pep-0475) + time docs versionchanged 3.5, docs.python.org/3/library/time.html | CPython 3.5+ | documented |
| PL-PY-045 | §4.5 set_wakeup_fd pattern: one-byte signal-number writes wake poll/select/loops; non-blocking fd required; buffer full → bytes lost (+ warn_on_full_buffer); drain before next poll; the bridge under every event-loop signal integration | signal.set_wakeup_fd(fd, *, warn_on_full_buffer=True) (behavior row) | ./python.md | signal docs (#signal.set_wakeup_fd) | [CPython] 3.8+ full form; Unix/Windows per availability | documented |
| PL-PY-046 | §4.6 POSIX trio pattern: pthread_sigmask + sigpending + sigwait; dedicated-signal-thread idiom moves handler logic to a normal thread; raise_signal for self-delivery (e.g. re-raise SIGINT after logging) | signal.pthread_sigmask(how, mask) / signal.sigpending() / signal.sigwait(sigset) / signal.raise_signal(signum) (behavior row) | ./python.md | signal docs (per-API) + PEP 475 context | Python 3.3+/3.8+; Unix | documented |
| PL-PY-047 | §4.7 Handler exceptions in main thread; documented SIGTERM→raise idiom; loop.add_signal_handler(signum, callback, *args) runs the callback as a normal loop callback (loop-interaction allowed), built over set_wakeup_fd, main-loop-thread only (ValueError/RuntimeError per docs), remove via remove_signal_handler | loop.add_signal_handler(signum, callback, *args) | ./python.md | asyncio event-loop docs, docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.add_signal_handler | [CPython] Unix event loops | documented |
| PL-PY-048 | §4.8 Windows deltas: signal() limited set; console-control emulation; os.kill special-cases CTRL_C_EVENT/CTRL_BREAK_EVENT (shared console, CREATE_NEW_PROCESS_GROUP) else TerminateProcess; send_signal maps SIGTERM→terminate(); pthread_sigmask/sigwait Unix-only | — | ./python.md | signal docs + os docs (#os.kill) + subprocess docs (send_signal note) | Windows (deltas), all in-scope versions | documented |
| PL-PY-049 | §4.2/§5.5 restore-signals rationale: Python-parent SIGPIPE-ignored inheritance into children is why restore_signals=True exists (child gets SIG_DFL pre-exec) | — | ./python.md | signal docs (SIGPIPE ignored) + subprocess docs (#subprocess.Popen restore_signals) | CPython + POSIX | documented |
| PL-PY-050 | §5.1 Popen args/shell semantics: sequence args recommended; shell=True on POSIX ≡ Popen(['/bin/sh', '-c', args[0], args[1], ...]) — extra sequence items are shell args (trap); executable= swaps program keeping argv[0]; bufsize maps to open() (0/1/-1/positive); text/encoding/errors | — | ./python.md | subprocess docs (#subprocess.Popen, Frequently Used Arguments), retrieved 2026-10-13 | Python 3.5+; POSIX /bin/sh | documented |
| PL-PY-051 | §5.1 stdio wiring: stdin/stdout/stderr ∈ None (inherit)/PIPE/DEVNULL/fd/file object; stderr=STDOUT merge; cwd= child chdir (POSIX: affects relative executable resolution); env= complete replacement | — | ./python.md | subprocess docs (#subprocess.Popen) | Python 3.x, POSIX+Windows | documented |
| PL-PY-052 | §5.2 preexec_fn hazard: called in child just before exec; “NOT SAFE to use in the presence of threads … could deadlock before exec”; docs redirect to env/start_new_session/process_group; RuntimeError in subinterpreters (3.8+) | Popen(..., preexec_fn=None) (behavior row) | ./python.md | subprocess docs (#subprocess.Popen, Warning blocks) | POSIX only; CPython 3.8+/3.12 subinterpreter rules | documented |
| PL-PY-053 | §5.3 fd inheritance: PEP 446 (3.4) non-inheritable-by-default; close_fds=True default since 3.2 closes all but 0/1/2; pass_fds forces close_fds=True and is the opt-in allowlist; os.pipe() results documented non-inheritable | Popen(..., close_fds=True, pass_fds=()) (behavior row) | ./python.md | PEP 446 (peps.python.org/pep-0446) + subprocess docs + os docs (#os.pipe) | CPython 3.4+ | documented |
| PL-PY-054 | §5.4 pre-exec parameter set: start_new_session=True → setsid() (3.2); process_group → setpgid(0, value) (3.11); group/extra_groups/user → setregid/setgroups/setreuid, strings via grp/pwd, user does not drop supplementary groups unless extra_groups=() (3.9); umask → umask() (3.9); all POSIX-only | Popen(..., start_new_session=False, process_group=None, group=None, extra_groups=None, user=None, umask=-1) (behavior row) | ./python.md | subprocess docs (#subprocess.Popen, per-parameter text + Added in version notes) | CPython 3.2/3.9/3.11 additions; POSIX | documented |
| PL-PY-055 | §5.5 restore_signals=True (default): SIG_IGN’d signals (currently SIGPIPE, SIGXFZ, SIGXFSZ per docs) restored to SIG_DFL in child pre-exec | Popen(..., restore_signals=True) | ./python.md | subprocess docs (#subprocess.Popen) | CPython 3.2+; POSIX | documented |
| PL-PY-056 | §5.6 poll/wait semantics: poll() non-blocking (None or returncode); wait() deadlock warning verbatim (PIPE + full child output); timeout waits: event-driven on Linux ≥ 5.3 (pidfd_open+poll) and macOS/BSD (kqueue) per 3.15 docs, else poll-sleep fallback; TimeoutExpired safe to catch and retry | Popen.poll() / Popen.wait(timeout=None) | ./python.md | subprocess docs (#subprocess.Popen.poll, #subprocess.Popen.wait incl. 3.15 versionchanged) | CPython 3.15.0 docs; Linux/macOS/BSD fast paths | documented |
| PL-PY-057 | §5.7 communicate(): concurrent drain of stdout/stderr + feed stdin + wait; timeout does NOT kill the child; retry after TimeoutExpired loses no output; post-timeout input is undefined behavior; documented kill-then-communicate cleanup pattern | Popen.communicate(input=None, timeout=None) -> (stdout_data, stderr_data) | ./python.md | subprocess docs (#subprocess.Popen.communicate) | Python 3.3+ timeout | documented |
| PL-PY-058 | §5.8 send_signal/terminate/kill/pid: send_signal no-op on completed process; terminate → SIGTERM (POSIX)/TerminateProcess (Windows); kill → SIGKILL (POSIX)/alias of terminate (Windows); returncode negative -N = signal N (POSIX only) | Popen.send_signal(sig) / Popen.terminate() / Popen.kill() / Popen.pid / .returncode | ./python.md | subprocess docs (per-method + returncode attribute) | Python 3.x; POSIX/Windows split | documented |
| PL-PY-059 | §5.9 Popen as context manager: on exit closes stdio fds and waits for the process (3.2+); __del__ emits ResourceWarning if child still running (3.6+); abandoned objects parked on module _active list and reaped by _cleanup() at next Popen creation (source-verified) | Popen context manager + Popen.__del__ (behavior row) | ./python.md | subprocess docs (context manager section + 3.6/3.8 versionchanged); CPython Lib/subprocess.py (del, _active, _cleanup) | CPython 3.2+ (ctx mgr), 3.6+ (warning), source main 2026-10-13 | documented |
| PL-PY-060 | §5.10 run() timeout contract: child killed and waited for, then TimeoutExpired re-raised (unlike bare communicate); check=True → CalledProcessError with .returncode/.cmd/captured output; creation time not interruptible on many platforms | subprocess.run(..., timeout=None, check=False) (behavior row) | ./python.md | subprocess docs (#subprocess.run) | Python 3.5+ | documented |
| PL-PY-061 | §5.12 Child-side pre-exec failure: exceptions raised in the child before the new program starts are re-raised in the parent (most commonly OSError/FileNotFoundError); caveat: with the posix_spawn fast path (3.8+), WSL/QEMU and friends report missing programs as non-zero child returncode instead | — | ./python.md | subprocess docs (Exceptions raised in the child process + 3.8 versionchanged) | CPython 3.8+ nuance | documented |
| PL-PY-062 | §5.11 shell=True injection surface: library “will not implicitly choose to call a system shell” — shell=False passes metacharacters safely; shell=True makes quoting the application’s responsibility; shlex.split tokenizes, shlex.join (3.8+) inverts split | shlex.split(s) / shlex.join(split_command) | ./python.md | subprocess docs (Security Considerations) + shlex docs, docs.python.org/3/library/shlex.html | Python 3.x; shlex.join 3.8+ | documented |
| PL-PY-063 | §5.14 os.fork: 0 in child / pid in parent, OSError on failure; DeprecationWarning when multiple threads detected (3.12); RuntimeError in subinterpreters (3.8+); macOS safety caveats; TLS warning; os.forkpty: fork + new pty controlling terminal, master fd non-inheritable | os.fork() / os.forkpty() | ./python.md | os docs (#os.fork, #os.forkpty incl. versionchanged notes) | POSIX; CPython 3.12 warning | documented |
| PL-PY-064 | §5.14 exec family: execv/execve/execvp/execvpe (+execl*) replace the process image, do not return, errors → OSError in caller; open file objects/descriptors not flushed — flush before exec | os.execv(path, args) / os.execve(path, args, env) / os.execvp(file, args) / os.execvpe(file, args, env) | ./python.md | os docs (#os.execv family) | Python 3.x, POSIX semantics (Windows variants limited) | documented |
| PL-PY-065 | §5.14 wait family + status decoding: os.wait() → (pid, raw 16-bit status); os.waitpid(pid, options) with WNOHANG; os.waitid(idtype, id, options) P_PID/P_PGID/P_ALL/P_PIDFD(Linux), WEXITED/WSTOPPED/WCONTINUED, WNOHANG → None when nothing ready; os.waitstatus_to_exitcode: WIFEXITED → status, WIFSIGNALED → -signum; unreaped children = zombies until parent exit | os.wait() / os.waitpid(pid, options, /) / os.waitid(idtype, id, options, /) / os.waitstatus_to_exitcode(status) | ./python.md | os docs (#os.wait, #os.waitpid, #os.waitid, #os.waitstatus_to_exitcode) | Python 3.x/3.9+ (waitstatus_to_exitcode); Unix | documented |
| PL-PY-066 | §5.14 posix_spawn pair: os.posix_spawn(path, argv, env, *, file_actions=None, setpgroup=None, resetids=False, setsid=False, setsigmask=(), setsigdef=(), scheduler=None) wraps posix_spawn(3) — fork+exec fast path with child setup expressed as POSIX_SPAWN_OPEN/CLOSE/DUP2 file_actions, no Python-in-child; posix_spawnp resolves via PATH; docs: “Most users should use subprocess.run() instead”; Popen uses it as fast path (3.8+) | os.posix_spawn(path, argv, env, *, file_actions=None, setpgroup=None, resetids=False, setsid=False, setsigmask=(), setsigdef=(), scheduler=None) / os.posix_spawnp(path, argv, env, *, ...) | ./python.md | os docs (#os.posix_spawn, #os.posix_spawnp) | Python 3.8+ | documented |
| PL-PY-067 | §5.14 os.spawn* legacy family: spawnl/spawnle/spawnlp/spawnlpe/spawnv/spawnve/spawnvp/spawnvpe with mode P_WAIT/P_NOWAIT; prefer subprocess | os.spawnl(mode, path, ...) (+ variants) | ./python.md | os docs (#os.spawnl family) | Python 3.x | documented |
| PL-PY-068 | §5.14 kill/killpg: os.kill(pid, sig), os.killpg(pgid, sig); Windows: CTRL_C_EVENT/CTRL_BREAK_EVENT special, anything else → unconditional TerminateProcess | os.kill(pid, sig, /) / os.killpg(pgid, sig, /) | ./python.md | os docs (#os.kill, #os.killpg) | Python 3.x; Unix / Windows delta | documented |
| PL-PY-069 | §5.14 pipes and dup2: os.pipe() → (r, w) non-inheritable (PEP 446); os.pipe2(flags) sets O_NONBLOCK/O_CLOEXEC atomically (Unix); os.dup2(fd, fd2, inheritable=True) → fd2, result inheritable by default | os.pipe() / os.pipe2(flags, /) / os.dup2(fd, fd2, inheritable=True) | ./python.md | os docs (#os.pipe, #os.pipe2, #os.dup2) | Python 3.3+ (pipe2), 3.4+ (inheritable kw) | documented |
| PL-PY-070 | §5.3 fd controls: os.get_inheritable(fd) → bool / os.set_inheritable(fd, inheritable) — the per-fd PEP 446 controls; setsid() for new sessions | os.get_inheritable(fd, /) / os.set_inheritable(fd, inheritable, /) / os.setsid() | ./python.md | os docs (#os.get_inheritable, #os.set_inheritable, #os.setsid) | Python 3.4+; Unix (setsid) | documented |
| PL-PY-071 | §5.13/§5.14 asyncio + multiprocessing: create_subprocess_exec/create_subprocess_shell → Process with coroutine communicate()/wait(), same terminate/kill mapping, StreamReader limit=65536, no run()-style auto-kill on timeout; multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None): fork/spawn/forkserver start methods, .daemon killed at parent exit, .exitcode None-while-running/negative-signal | asyncio.create_subprocess_exec(program, *args, stdin=None, stdout=None, stderr=None, limit=65536, **kwds) / asyncio.create_subprocess_shell(cmd, ...) / multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None) | ./python.md | asyncio-subprocess docs + multiprocessing docs (docs.python.org/3/library/asyncio-subprocess.html, /3/library/multiprocessing.html) | Python 3.x; start-method semantics per platform | documented |
| PL-PY-072 | §6.1 GIL effects in scope: one bytecode-executing thread at a time; signal handling needs main-thread bytecode; GIL-holding C extensions delay delivery; memory visibility between threads is implementation artifact (free-threaded build changes it) | — | ./python.md | signal docs + chapter §6.1; companions ../python/gil.md, ../python/free-threaded.md | [CPython]; build-dependent (free-threaded 3.13+) | documented |
| PL-PY-073 | §6.2 fork × threads: 3.12 DeprecationWarning when multi-threaded detected (verbatim docs quote; “never been safe to mix threading with os.fork() on POSIX”); os.register_at_fork(*, before=None, after_in_parent=None, after_in_child=None) as the sanctioned hook set (3.7+); multiprocessing fork start method inherits the hazard, spawn avoids it | os.register_at_fork(*, before=None, after_in_parent=None, after_in_child=None) (behavior row) | ./python.md | os docs (#os.fork versionchanged 3.12, #os.register_at_fork) | CPython 3.7+/3.12+; POSIX | documented |
| PL-PY-074 | §6.3 buffering × fork: fork duplicates userspace buffers (Python file objects + C stdio); exec does not flush them (docs quote) → flush before exec; child must _exit or exec, never return through parent cleanup; Popen immune by construction (child stdio re-wired pre-exec) | — | ./python.md | os docs (#os.execvpe flush note) + chapter §6.3 | POSIX + CPython | documented |
| PL-PY-075 | §6.4 fd ownership rule: non-inheritable-by-default prevents socket/lock leakage into children; dup2/pipe2(O_CLOEXEC) are the opt-in mechanisms for raw fork/exec code; CPython sets the flags at creation per PEP 446 | — | ./python.md | PEP 446 + os docs (inheritable API) | CPython 3.4+ | documented |
| PL-PY-076 | §6.5 daemon threads: abruptly stopped at shutdown; “Their resources … may not be released properly”; no finally/exit/del for in-flight work; program exits when no alive non-daemon threads remain (threading._shutdown joins them) | threading.Thread(..., daemon=None) (behavior row) | ./python.md | threading docs, docs.python.org/3/library/threading.html (daemon note + _shutdown behavior) | Python language + CPython | documented |
| PL-PY-077 | §6.6 deadlocks: wait()+PIPE vs child filling the OS pipe buffer (docs warning verbatim); symmetric join()-vs-full-pipe shutdown deadlock; pipe capacity is OS-level (Linux default 64 KiB; pipesize=/F_SETPIPE_SZ hints); asyncio StreamReaders bound by limit instead of threads | — | ./python.md | subprocess docs (wait note) + chapter §6.6 + os docs (pipe2/pipesize) | POSIX/Linux + CPython | documented |
| PL-PY-078 | §6.7 races: preexec_fn races parent threads for duplicated runtime state; subprocess._cleanup’s own documented thread race on _active.remove (harmless, source comment); guidance: single owner thread for Popen objects | — | ./python.md | CPython Lib/subprocess.py (_cleanup comment) + subprocess docs (preexec_fn warning) | CPython main 2026-10-13 | documented |
| PL-PY-079 | §6.8 leaks: unclosed parent-side pipe fds → ResourceWarning; abandoned running Popen → ResourceWarning at del + _active reaping at next Popen creation + final reaping at interpreter exit; raw os.fork parents must wait/waitpid or zombies accumulate until parent exit; GC/finalization is backstop (PEP 442), not the release mechanism | — | ./python.md | subprocess docs (3.6 versionchanged) + CPython Lib/subprocess.py + gc docs | CPython 3.6+ | documented |
| PL-PY-080 | §6.9 cleanup hierarchy: with (deterministic, exception-safe) → try/finally → explicit close() on error paths → atexit (process lifetime, LIFO) → del/GC backstop → OS reclamation at _exit/death; Popen with closes stdio fds and waits (blocking) on block exit | — | ./python.md | Data model + atexit + subprocess docs (context manager) | Python language + CPython | documented |