Skip to content

Import #776's teardown half: decide on the root's liveness, not on taskkill's message - #15

Merged
Edo771977 merged 3 commits into
mainfrom
claude/focused-carson-khonz0
Sep 22, 2026
Merged

Edo771977 merged 3 commits into
mainfrom
claude/focused-carson-khonz0

Conversation

@Edo771977

Copy link
Copy Markdown
Owner

One new PR upstream since the last sweep. It bundles three changes; one is worth having here, and the other two are not.

347/347, full suite run twice. tsc clean.

What was wrong here

Our Windows branch trusted taskkill's exit status and, failing that, matched its message. The localization hole the PR leads with — looksLikeMissingProcessMessage only recognising English, so a French "introuvable" fell through — was already closed here: we carry the status === 128 check, which is language-independent.

The part that was broken is the one their commit message buries: any other non-zero status threw at the caller, and taskkill produces one routinely. /T walks the tree and then terminates each entry, so a short-lived git or cmd helper that exits in between makes taskkill report a failure although the root did die. A throw out of terminateProcessTree() lands in teardown paths that cannot tell that apart from a real failure.

Now:

  • the root is probed before taskkill is spawned at all — a root that is provably gone returns {attempted: false, delivered: false, method: null} and costs no process
  • after a non-zero status, the root's own liveness decides
  • delivered accordingly describes the root, exactly like the process-group SIGTERM on other platforms. It never proved every descendant was gone, and now says so

Changes on top of the import

  • their isProcessAlive() is dropped for this fork's isPidAlive(), and the new gate is isProvablyGone(): only ESRCH proves absence. Their reading (anything but ESRCH is alive) is the right one for "should I kill"; the question at this gate is the inverse — "may I skip the kill" — and skipping it on a root we merely cannot read leaks the whole tree
  • two of our tests now inject killImpl. Without it they call the real process.kill(1234, 0), so their outcome depended on whether pid 1234 happened to exist on the machine running the suite. That is exactly the class of environment-dependent test this fork has spent two rounds removing

Not taken

Their change Why not
path.posix.join in createBrokerEndpoint the unix: branch is unreachable on Windows, so on every platform this fork runs it produces the same string
2s timeout in sendBrokerShutdown ours is already bounded, with refused/unreachable semantics and a retry — strictly ahead of it

Their two test-portability fixes are kept, though: a .cmd shim instead of a symlink for the fake node, and a skip when Windows refuses to create the broken-symlink fixture without Developer Mode. They change no behavior and let the suite run on a real Windows host — which is the one check this fork still cannot do from here.

Sabotage matrix

sabotage which tests fail
pre-kill probe removed 4, including "skips taskkill when the Windows process is already absent"
back to matching the message instead of liveness 2, including "uses liveness instead of localized taskkill output"
a permission error read as absence 2, including "does not treat a Windows preflight permission error as missing"

🤖 Generated with Claude Code

https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg


Generated by Claude Code

Kenshiro787 and others added 3 commits September 21, 2026 00:00
sendBrokerShutdown waited forever when the broker accepted the
connection but never replied, so the SessionEnd hook never returned
(observed as "Hook cancelled"). It now gives up after a timeout and
destroys the socket; a regression test covers a mute broker.

terminateProcessTree treated any non-zero taskkill status as a failure.
On Windows, taskkill /T enumerates the tree and then terminates each
entry; a short-lived descendant (git/cmd helpers spawned by the worker)
that exits in between makes taskkill report "The operation attempted is
not supported" with status 128 even though the root process was killed.
The root's liveness is now checked before treating that as an error,
which fixes the flaky cancel integration test (reproduced 7/8 runs).

Also make the unix endpoint path POSIX-joined and adapt Windows test
fixtures: skip the broken-symlink case without Developer Mode, use a
node.cmd shim instead of a symlink, and propagate USERPROFILE.

Full suite on Windows 11 / Node 26.1.0: 93 passed, 0 failed, 1 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review of the previous commit found that terminateProcessTree could
report delivered: true for a Windows process that was already gone
before the call: looksLikeMissingProcessMessage only recognised English
taskkill messages, so on a localised system (French "introuvable") the
"not found" status fell through to the liveness check and was taken as
a successful kill.

terminateProcessTree now probes the root with process.kill(pid, 0)
before running taskkill. A root that is already absent returns
{ attempted: false, delivered: false, method: null } without invoking
taskkill, in every system language; looksLikeMissingProcessMessage and
the text-based branch are removed. EPERM on the probe still means the
process exists, so taskkill runs as before. The post-taskkill liveness
check and the ENOENT fallback are unchanged.

Comments now state the scope of delivered: it describes the root only,
like the process-group SIGTERM on other platforms, and does not prove
that every descendant is gone. Narrow TOCTOU windows (root exiting on
its own between probe and taskkill, PID reuse before the second probe)
are accepted; none of the callers reads the return value.

Tests: French taskkill output with liveness probes, taskkill skipped
when the root is already absent, EPERM on the probe not treated as
absent, ENOENT fallback preserved, non-Windows path unchanged; existing
mocks now lock the killImpl(pid, 0) call. Against the previous
process.mjs, 4 of the 8 tests fail.

Windows 11 / Node 26.1.0, run from native cmd.exe: process.test.mjs
8 passed; cancel integration test 3/3; full suite 98 tests, 97 passed,
0 failed, 1 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…l's message

Taken for terminateProcessTree(); the PR's other two changes are not.

What it fixes here. Our win32 branch trusted taskkill's exit status and,
failing that, matched its message. Status 128 already covered "no such
process" in any language, so the localization hole the PR leads with was
closed here — but any *other* non-zero status threw at the caller, and
taskkill produces one routinely: /T walks the tree and then terminates
each entry, so a short-lived git or cmd helper that exits in between makes
it report a failure although the root did die. A throw out of
terminateProcessTree lands in teardown paths that have no way to tell that
apart from a real failure.

Now the root is probed before taskkill is spawned at all — a root that is
provably gone returns attempted: false and costs no process — and after a
non-zero status the root's own liveness decides. `delivered` accordingly
describes the root, exactly like the process-group SIGTERM on other
platforms; it never claimed to prove every descendant was gone.

Changes on top of the import:

- their isProcessAlive() is dropped for this fork's isPidAlive(), and the
  new gate is isProvablyGone(): only ESRCH proves absence. Their reading
  (anything but ESRCH is alive) is right for "should I kill", but the
  inverse question here is "may I skip the kill", and skipping it on a
  root we merely cannot read leaks the whole tree
- two of our tests now inject killImpl. Without it they call the real
  process.kill(1234, 0), so their outcome depended on whether pid 1234
  happened to exist on the machine running the suite

Not taken: `path.posix.join` in createBrokerEndpoint (the unix branch is
unreachable on Windows, so it changes nothing here), and the 2s timeout in
sendBrokerShutdown (ours is already bounded, with refused/unreachable
semantics and a retry). Their two test-portability fixes are kept: they
let the suite run on a Windows host without Developer Mode, and change no
behavior.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
@Edo771977
Edo771977 merged commit 3d91572 into main Sep 22, 2026
1 check passed
@Edo771977
Edo771977 deleted the claude/focused-carson-khonz0 branch September 22, 2026 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants