Fix Windows SessionEnd hang and taskkill false failures - #776
Open
Kenshiro787 wants to merge 2 commits into
Open
Kenshiro787 wants to merge 2 commits into
Kenshiro787 wants to merge 2 commits into
Conversation
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>
Edo771977
added a commit
to Edo771977/codex-plugin-cc
that referenced
this pull request
Sep 22, 2026
Import openai#776's teardown half: decide on the root's liveness, not on taskkill's message
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two Windows-specific problems in the plugin's process teardown, observed on Windows 11 with Node 26:
sendBrokerShutdownwaited forever when the broker accepted the connection but never replied, so Claude Code reported the hook as cancelled. It now gives up after a timeout (2 s by default) and destroys the socket.cancelfailed although the task had been killed.terminateProcessTreetreated any non-zerotaskkillstatus as a failure. On Windows,taskkill /PID <root> /T /Fenumerates the tree first and then terminates each entry; a short-lived descendant (git/cmd helpers spawned by the worker) that exits in between makestaskkillreport "The operation attempted is not supported" and exit non-zero even though the root process was killed. This reproduced in 7 of 8 controlled runs and made thecancelintegration test flaky (0/5 before the change, 3/3 after).Changes
broker-lifecycle.mjs:sendBrokerShutdown(endpoint, timeoutMs = 2000)resolves on timeout and clears the timer on data/error/close.process.mjs: on Windows,terminateProcessTreeprobes the root withprocess.kill(pid, 0)before runningtaskkill. A root that is already gone returns{ attempted: false, delivered: false, method: null }without invokingtaskkill. After a non-zerotaskkillstatus, the root's liveness decides the outcome: root gone means delivered, root still alive keeps the previous behaviour and throws. The English-onlylooksLikeMissingProcessMessagefilter is removed, so the result no longer depends on the localizedtaskkillmessage text (a French "introuvable" used to fall through and be reported as a successful kill). Comments state thatdelivereddescribes the root only, like the process-groupSIGTERMon other platforms; it does not prove that every descendant is gone. Non-Windows code paths and theENOENTfallback are unchanged.broker-endpoint.mjs: the unix endpoint path usespath.posix.join, so it stays a valid socket path when computed on Windows.node.cmdshim instead of a symlink, and propagateUSERPROFILEnext toHOME.Tests
sendBrokerShutdowntimes out against a mute broker;terminateProcessTreewith localizedtaskkilloutput and liveness probes,taskkillskipped when the root is already absent,EPERMon the probe not treated as absent,ENOENTfallback preserved, non-Windows path unchanged. Against the previousprocess.mjs, 4 of the 8 process tests fail.cmd.exe: 98 tests, 97 passed, 0 failed, 1 skipped (the symlink fixture without Developer Mode). Thecancelintegration test passed 3/3 consecutive runs.Known limitations
deliveredis root-scoped. A descendant thattaskkillcould not stop after the root died is not detected, which matches the process-group signal semantics on other platforms; none of the callers reads the return value.taskkill; PID reuse before the second probe). They only affect the reported status, not the teardown itself.🤖 Generated with Claude Code