Skip to content

worktree-reconcile: the _pid_alive probe can signal or terminate a process on Windows instead of testing whether it exists #212

Description

@Paul-Kyle

_pid_alive in palinode/cli/worktree.py decides whether a worktree's owning process is still running, and palinode worktree-reconcile removes worktrees on a False:

try:
    os.kill(pid, 0)
    return True
except ProcessLookupError:
    return False

On POSIX, signal 0 is the documented way to ask "does this process exist" without delivering anything. On Windows it is not a query at all. CPython's os.kill treats 0 as CTRL_C_EVENT and attempts GenerateConsoleCtrlEvent; if that call fails it falls through to OpenProcess + TerminateProcess. So the probe either delivers a console control event to the target or tries to kill it — and which of those happens depends on console attachment, not on anything we control.

The function's own docstring states the invariant this breaks: a PermissionError means the process exists but belongs to another user, and any other OS error means we cannot tell — "both are treated as alive so we never remove a worktree whose owner might still be running." On Windows it is not asking that question.

Observed on two hosts and two Python versions. @kevin-lozada-santos on Windows 11 / Python 3.11.15 and @Kaap10 on Windows 11 / Python 3.12.7 both report the suite stalling at tests/test_worktree_reconcile.py. Eight tests are common to both inventories as having no completed native result:

test_pid_alive_self_true_and_dead_false
test_dead_clean_upstream_is_removed
test_alive_lock_is_skipped
test_dirty_tree_is_skipped
test_no_upstream_is_skipped
test_apply_removes_only_dead_clean_upstream
test_cli_dry_run_default_removes_nothing
test_cli_execute_removes

These are tests the runner never finished after the stall, not eight independently diagnosed failures. Both inventories are posted in full on #169.

What is wanted: a non-signalling existence query on Windows — OpenProcess with PROCESS_QUERY_LIMITED_INFORMATION, reading the exit code, and closing the handle — while POSIX keeps os.kill(pid, 0) unchanged. Preserve the fail-safe direction: anything undeterminable must still count as alive, because the caller deletes worktrees on a False.

Tests: three outcomes, and none of them may signal the target — live, dead, and unknown/undeterminable, with the last resolving to alive. That third one is the property the docstring promises and the one whose failure deletes someone's work. Make sure any handle the probe opens is closed on every path, including the error paths.

Not in scope: the other Windows suite failures. Those are #169's surface and are being carved separately.

Credit: reported by @kevin-lozada-santos, independently observed on 3.12 by @Kaap10.

Reserved: @kevin-lozada-santos and @Kaap10 have first refusal on this one, since it came out of their runs. If neither has claimed it by 21 September 2026 at 14:00 UTC, it is open to anyone — comment here and please wait to be assigned before opening a PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions