phasespace-labs / phasespace-labs/palinode

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

Open
#212 3 comments 1 reaction 1 assignee Claimed by @Kaap10 View on GitHub
bug
Dominant language
Python
Stars
39
Forks
42
Avg merge
1d 1h
Merged PRs (30d)
37

Description

`_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`:

```python
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.**

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.