Skip to content

Fix timeout child-process lookup on non-GNU systems - #2236

Merged
Byron merged 2 commits into
gitpython-developers:mainfrom
aprylewu:codex-fix-portable-timeout-child-lookup
Sep 13, 2026
Merged

Fix timeout child-process lookup on non-GNU systems#2236
Byron merged 2 commits into
gitpython-developers:mainfrom
aprylewu:codex-fix-portable-timeout-child-lookup

Conversation

@aprylewu

Copy link
Copy Markdown
Contributor

Git.execute(..., kill_after_timeout=...) currently finds direct child processes with ps --ppid. On macOS, that command fails and only the parent receives SIGKILL; a child can continue running and keep the captured output pipes open after the timeout.

Use pgrep -P to enumerate direct children, falling back to POSIX ps -A -o pid= -o ppid= when pgrep is not installed. The fallback filters by the target parent's PID. Both enumeration subprocesses are waited for and their pipes closed. The existing parent-first signal order and direct-child scope are preserved.

The regression runs a real parent/child pair through Git.execute. Before the change, the child survives the timeout and writes a marker; after the change, it is terminated, both with native pgrep and with pgrep unavailable. A separate test checks that the fallback excludes unrelated processes and grandchildren.

Fixes #1756.

Validation on macOS 27.0, Python 3.13.5:

  • The two real-process regressions failed on the original code.
  • python -m pytest test/test_git.py -q --no-cov: 105 passed, 1 skipped.
  • ruff check . and ruff format --check . (0.16.5).
  • codespell git/cmd.py test/test_git.py (2.4.3).
  • mypy (1.18.2): 45 source files.
  • basedpyright --warnings (1.39.9): no errors or warnings.
  • git diff --check.

Linux and Cygwin were not run locally. The real-process regression skips Cygwin, whose default ps does not support these POSIX options; the documented tool requirements still apply. This does not add native Windows support or change the separate timeout implementation used by Remote.fetch, pull, and push.

I am an AI agent (OpenAI Codex) acting on behalf of Mingyang Wu (aprylewu). I prepared this implementation and ran the checks above.

Git.execute used ps --ppid to find direct children before enforcing
kill_after_timeout. On macOS this option is rejected: the parent is killed,
but a child can continue running and hold captured output pipes open.

Use pgrep -P for the child lookup, with POSIX ps PID/PPID output as a
fallback when pgrep is absent. Filter the fallback by the original parent
PID and reap the lookup subprocess in both paths. Keep the existing
parent-first SIGKILL order, direct-child scope, and Windows guard, and
update the documented command requirements. Systems without either lookup
facility and the existing PID-reuse race remain limitations.

Add real-process regressions for native pgrep and the ps fallback, plus a
test that excludes unrelated processes and grandchildren from the fallback.
Both real-process cases failed on the original code on macOS. The command
module now passes 105 tests with 1 skip on macOS 27.0 / Python 3.13.5.
Ruff check and format, codespell, mypy (45 files), basedpyright, and diff
whitespace checks pass. Linux and Cygwin were not run locally; Cygwin's
default ps lacks the required options, so the real-process cases skip it.

Fixes gitpython-developers#1756

Signed-off-by: Mingyang Wu <129849514+aprylewu@users.noreply.github.com>
@Byron

Byron commented Sep 13, 2026

Copy link
Copy Markdown
Member

Thanks a lot!

@Byron
Byron force-pushed the codex-fix-portable-timeout-child-lookup branch from 3686b6a to be1f168 Compare September 13, 2026 03:17
Several public annotations rejected supported inputs or lost the relationship
between input options and return types. Describe Git.execute process, text,
bytes, and extended-output results with overloads, accept stdin file
descriptors, and account for absent stdout. Correct remote-removal, object,
database, blame, and index-entry types, preserve entry subclasses and the
supported tuple shapes, and accept streams with only the required read or
write methods. Normalize absent previous stderr before appending process
errors in AutoInterrupt.wait.

Add runtime and static regressions for these interfaces, include them in
mypy and basedpyright, make required imports explicit, and reduce the
basedpyright baseline to the remaining diagnostics. Keep mock available to
typecheck the Python 3.7 import branches.

Make the output-type regression emit its payload without a newline. Its
original print call produced CRLF on Windows; Git.execute strips the final
LF as documented, leaving a carriage return that failed the test in all
11 Windows jobs. Omitting the newline keeps the same text, bytes, and tuple
assertions independent of platform newline translation.

Validation on macOS with Python 3.12.14: all four test/test_typing.py tests
pass. Forcing the child stdout to translate newlines to CRLF reproduces the
old failure and passes with the corrected command. Mypy passes for 46
source files; basedpyright --warnings reports no errors or warnings; Ruff
lint and format checks for test/test_typing.py and git diff --check pass.
Native Windows validation is delegated to the PR CI matrix.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>
@Byron
Byron force-pushed the codex-fix-portable-timeout-child-lookup branch from be1f168 to d66edad Compare September 13, 2026 03:57
@Byron
Byron merged commit a9913c7 into gitpython-developers:main Sep 13, 2026
53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Git.execute's kill_after_timeout callback assumes procps

2 participants