fix(hook): terminate the permission watcher on a signal - #3
Open
omariqbalnaru wants to merge 1 commit into
Open
omariqbalnaru wants to merge 1 commit into
omariqbalnaru wants to merge 1 commit into
Conversation
The SessionStart watcher registered one trap for both cleanup and signals:
trap 'rmdir "$watcher_dir" 2>/dev/null' 0 HUP INT TERM
A caught signal only runs the handler; execution then resumes at the next
statement, which is the watcher's `while :` loop. So HUP/INT/TERM removed the
marker directory and the watcher kept polling — it never exited. Nothing else
reaps it: Command Code has no Exit hook event, and a pane's kill reaches only
the pane's own process group, so a leaked watcher survives cmd, the pane, and
the herdr server.
Observed live: a `herdr-status.sh` watcher reparented to PID 1, still running
`sleep 1` after 8+ minutes, writing to a unix socket whose herdr server had
already exited, and surviving `kill -TERM`.
Fix: split the traps — `0` cleans up and runs on every exit path, while
HUP/INT/TERM clean up and then `exit 0`. Also record the watcher's pid in the
marker dir so a future leak is diagnosable instead of invisible; `$!` is read
in the parent, since `$$` inside the subshell is the parent's pid. Cleanup
removes that pid file before `rmdir`, which only removes empty directories.
Verified with a regression test (tests/hook.test.sh #22) that starts a real
SessionStart watcher and asserts it dies on SIGTERM:
upstream: PASS=40 FAIL=2 (watcher pid never published / survived SIGTERM)
fixed: PASS=42 FAIL=0
The test unset HERDR_PERMISSION_MAX_SCANS so the watcher would loop
indefinitely without the fix; the suite's single-scan default would otherwise
mask the leak. launch.test.sh's 4 failures are pre-existing and unrelated.
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.
Problem
start_blocking_prompt_watcher()registered a single trap for cleanup and signals:A caught signal only runs the handler — execution then resumes at the next
statement, which is
watch_blocking_prompt'swhile :loop. So HUP/INT/TERMremoved the marker directory and the watcher kept polling. It never exited.
Nothing else reaps it. Command Code has no
Exithook event, and a pane's killreaches only the pane's own process group, so a leaked watcher outlives
cmd,the pane, and the herdr server.
Observed live on macOS:
Reparented to PID 1, still looping after 8+ minutes, writing to a socket whose
herdr server had exited — and it survived
kill -TERM.Reproduced in isolation:
Fix
Split the traps:
0cleans up on every exit path, while HUP/INT/TERM clean upand then
exit 0.Two supporting details:
diagnosable rather than invisible.
$!is read in the parent, because$$inside the subshell is the parent's pid.rmdir, which only removes emptydirectories — otherwise the fix would trade a leaked process for a leaked
directory.
Verification
New regression test (
tests/hook.test.sh#22) starts a realSessionStartwatcher and asserts it terminates on SIGTERM and leaves no marker dir. It
unsets
HERDR_PERMISSION_MAX_SCANS, because the suite's single-scan defaultwould let the watcher exit on its own and mask the leak.
tests/hook.test.shce4aa8f)PASS=40 FAIL=2— watcher pid never published; survived SIGTERMPASS=42 FAIL=0Full suite after the change:
common4/4,hook42/42,install5/5,launch4/4 passing. (launch.test.shreportsPASS=4 FAIL=4for theresume-last/resume-namedarg assertions both before and after thischange — pre-existing and unrelated, so not touched here.)
Scope
Deliberately narrow: this makes the watcher reapable. It does not claim to
explain every orphaned process a user might see after force-quitting a pane —
the
NOTES.mdforce-quit gap, where no exit hook exists to report a finalstate, is a separate and larger problem.