[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71P0DXPMG95R8EJ83THQMBJ":3,"skill-same:01M71P0DXPMG95R8EJ83THQMBJ":99},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":44,"commitSha":45,"skillDirectory":46,"forksCount":47,"repositoryHtmlUrl":48,"repositoryHomepageUrl":6,"ownerHtmlUrl":49,"ownerBlogUrl":50,"ownerDescription":51,"updatedAt":52,"install":53,"resources":66},"01M71P0DXPMG95R8EJ83THQMBJ","verify-subagent-run",null,"Model-check the pi-subagent run lifecycle with TLA+, so that no process of a run outlives its launcher under any order of kills. Use after changing agent\u002Fextensions\u002Fsubagents\u002F or upgrading Pi or Effect, before reporting the change as working, and when `scripts\u002Fcheck` fails.","drsh4dow\u002Fpi-setup","drsh4dow","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F45247070?v=4","01M71P068NASGHSWS11VWW416N","Daniel Moretti","01M71P0698BH81N65NP04X9NVN",10,"MIT","https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverify-subagent-run\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"verify subagent run","verify","run","subagent","scripts","process","change","order","with","check","extensions","when","TLA+","use","lifecycle","pi","launcher","model","effect","as","2026-11-10 10:56:23",false,"---\nname: verify-subagent-run\ndescription: Model-check the pi-subagent run lifecycle with TLA+, so that no process of a run outlives its launcher under any order of kills. Use after changing agent\u002Fextensions\u002Fsubagents\u002F or upgrading Pi or Effect, before reporting the change as working, and when `scripts\u002Fcheck` fails.\n---\n\n# Verify the subagent run lifecycle\n\n`pi-subagent` runs a child Pi in a herdr pane. Four processes share one run directory: the launcher (`bin\u002Fpi-subagent.ts`), the pane child (`bin\u002Fpane-child.ts`), pi, and possibly a descendant Pi that an extension starts before pi's `session_start`. When the `pi` command is a wrapper that forks Pi, such as a Volta shim, the wrapper is a fifth. [`SubagentRun.tla`](SubagentRun.tla) models every order in which their steps and the kills can happen, and TLC checks all of them.\n\n- **Properties.** Once the launcher has exited, by any path including SIGKILL, the pane child, pi, and the descendant all exit too (`NoProcessOutlivesLauncher`). Once any kill arrives, the launcher exits (`KillEndsRun`). Once all of them have exited, the run directory and the pane are gone (`NoRunLeftBehind`). Pi never leaves the run to another Pi, and the descendant never claims it (`OnlyPiTakesTheTask`).\n- **Harm.** A subagent that outlives its launcher keeps running its task, spending tokens and editing the worktree, after its parent thinks it is dead. A launcher that a kill does not end leaves its caller waiting forever. A run directory left behind keeps `launch.json`, which holds the launcher's environment, and a pane left behind clutters the parent's workspace. A descendant that takes the task runs it without the launcher's arguments, such as its model, and pi never ends the run. A pi that leaves the run to another Pi fails it after 60 s.\n\n## What the check covers\n\nRun the check after any change to these files, or to the Pi or Effect version in `bun.lock`. Each `\\* code:` citation in the spec names one of these functions. Every model config runs the same spec, so they all cover the same code.\n\n| File under `agent\u002Fextensions\u002Fsubagents\u002F` | Functions                                                                                                                             |\n| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |\n| `bin\u002Fpi-subagent.ts`                     | `main`, `runInPane`, `observeChild`, `addObserver`, `awaitChild`, `stopPiGroup`, `closePane`, `SIGNAL_EXIT_CODES`                     |\n| `bin\u002Fpane-child.ts`                      | `runPaneChild`, `runPi`, `watchLauncher`, `launcherExit`, `abandonRun`                                                                |\n| `process-group.ts`                       | `stopProcessGroup`                                                                                                                    |\n| `herdr.ts`                               | `sweepAbandonedRuns`, `removeAbandonedRun`, `runOwnerAlive`                                                                           |\n| `index.ts`                               | `claimRun`, `deliverTask`, `finish`, `stopChildrenInheritingRun`                                                                      |\n| `launch.ts`                              | `runFiles`, `runDirectoryPrefix`, `parseRunDirectoryName`, `runFlag`, modeled as the run directory's files and name, with no citation |\n\nThe spec also cites Pi's `interactive-mode.js` (`init`, `shutdown`, `uncaughtCrash`) and Effect's `NodeChildProcessSpawner.ts` (`terminateProcessGroup`) under `node_modules\u002F`, as installed from `bun.lock`. The sync commit below has `@earendil-works\u002Fpi-coding-agent` 1.2.0 and `effect` 4.0.4.\n\nIn sync at `5e34079ec0fd2fa4976b249a81866e3533bf50d7`. Move this commit forward only after you confirm that the model describes the code above at the new commit.\n\nA change needs a model update when it adds, removes, or reorders a step that touches shared state. Shared state is the run directory and its files (`started`, `launch.json`, `claim`, `exit`), the pane, a signal, or whether a process is alive. A change to logging, report text, or stdout touches none of it.\n\n## Run the check\n\nFrom the checkout root:\n\n```bash\n.agents\u002Fskills\u002Fverify-subagent-run\u002Fscripts\u002Fcheck\n```\n\nIt needs Java 11 or newer on `PATH`, or in `JAVA`. The first run downloads TLC 1.7.4 to `${XDG_CACHE_HOME:-~\u002F.cache}\u002Fpi-setup\u002F` and checks its sha256. A run takes about 50 s. `vp run verify` does not run it.\n\nThe script does three things:\n\n1. It checks that every `\\* code:` citation in the spec still matches its file. A citation names a file, a function, and a line copied from the code.\n2. It runs three models that must hold. [`SubagentRun.cfg`](SubagentRun.cfg) is the main model. [`ShutdownHangs.cfg`](ShutdownHangs.cfg) lets pi's graceful shutdown hang. [`ForkingPi.cfg`](ForkingPi.cfg) is the main model with a `pi` command that forks Pi.\n3. It runs [`DescendantShutdownHangs.cfg`](DescendantShutdownHangs.cfg) and [`ForkingPiShutdownHangs.cfg`](ForkingPiShutdownHangs.cfg), which reproduce known findings and must be violated. See [Known findings](#known-findings).\n\nExit 0 means every citation is current and each model matched the `\\* expect:` line at the top of its config. For a violated model, the script prints the counterexample as numbered steps. Each step names the action, the file and function it models, and the variables that changed. The full TLC output is in `${TMPDIR:-\u002Ftmp}\u002Fverify-subagent-run\u002F`.\n\n## Update the model for an intended change\n\n1. List the changed hunks with `git diff` from the sync commit on the files in [What the check covers](#what-the-check-covers).\n2. For each hunk, find the actions whose citation sits in it, or whose behavior it changes. Search the spec for the function name.\n3. Edit each action to do what the new code does. Keep one action per step that another process can observe, and fold purely local work into the neighboring step. Name a new action after the code, prefix it with `L` (launcher), `C` (pane child), or `P` (Pi), add it to `LauncherStep`, `ChildStep`, or `PiStep`, and put a `\\* code:` citation above it. Add a newly cited function to the table in [What the check covers](#what-the-check-covers).\n4. Run the check.\n\nThe update is done when the check exits 0, or when it fails only on a finding you report. If the property fails after a faithful update, the change broke it. Report the counterexample. The properties, the fairness conditions, and `EnabledKills` stay as they are.\n\n## Read a failure\n\n- **`stale citation`.** The cited line changed. Open the action above the citation and compare it with the new code. If the behavior is the same, copy the new line into the citation. If the behavior changed, update the action first.\n- **`model error (TypeOK)`.** The spec gives a variable a value outside its type. Fix the spec.\n- **`TLC error`.** TLC could not parse or evaluate the spec. The script prints the error lines.\n- **`violated, expected holds`.** Walk the counterexample and open the cited code for each step. Then classify it:\n  - **Modeling error.** A step does something the code cannot do, such as reading a file the code reads later. Fix that action to match the code.\n  - **Timing artifact.** The model has no clock, so a trace can need a step to finish faster than the code can. An example is pi registering its signal handlers microseconds after it spawns. Report the durations involved instead of calling it a bug.\n  - **Real bug.** Every step is possible in the code, in that order. Report it as steps in the code's terms, such as \"the launcher reads no `started`, then the pane child writes `started`\".\n- **`holds, expected violated`.** The known finding no longer reproduces. Confirm which change fixed it, then update [Known findings](#known-findings) and the config's `\\* expect:` line.\n\nRead every result with the **statechart** skill, which says how to chart a broken PROPERTY as an invariant over settled states. `NoProcessOutlivesLauncher`, `KillEndsRun`, and `NoRunLeftBehind` are properties. In a scratch copy of the spec, `System` is the disjunction of the actions in `Fairness`, and the invariant for `NoProcessOutlivesLauncher` is `Settled \u002F\\ launcher = \"gone\" => RunProcessesGone`. No reader takes an action here, so list the kills and Pi's own steps in `readerActions`.\n\n## What the model assumes\n\n- **Process group.** Pi and the descendant stay in the pane child's process group, the group whose id is in `started`. A process that leaves it, such as a background terminal's job, is outside the model.\n- **Hangups.** The pane child is the controlling process of the pane's terminal. Closing the pane sends it SIGHUP. When it exits, the kernel sends SIGHUP to the group and herdr removes the pane.\n- **Pi's signals.** Before `InteractiveMode.init` registers its handlers, SIGTERM or SIGHUP ends Pi. After that, either signal starts a graceful shutdown. `SubagentRun.cfg` assumes every graceful shutdown finishes. The configs whose names end in `ShutdownHangs` let one never finish.\n- **Run flag.** The pane child passes `--subagent-run=\u003Crun directory>` to the `pi` command, and a wrapper passes its arguments on to Pi. A descendant has only the arguments that its extension gives it.\n- **Wrapper.** With `PiForks`, the pane child spawns a wrapper that forks Pi into the same process group and waits for it, as a Volta shim does. The wrapper handles neither SIGTERM nor SIGHUP, as the shim in the review's reproduction does not, so a signal to the wrapper alone leaves Pi running.\n- **Pi's progress.** Nothing else Pi does is assumed. It can stop at a startup prompt, work forever, or crash at any step.\n- **Kills.** SIGKILL ends a process at once. Each kill arrives at most once and at any moment. The kills are SIGTERM to the launcher (SIGINT and SIGHUP behave the same), SIGKILL to the launcher, an external close of the pane, and SIGKILL to the pane child.\n- **herdr.** Every herdr call succeeds. Once told to run the pane child in an open pane, herdr eventually starts it.\n- **Sweeps.** Some later sweep runs, because each `pi-subagent` run and each Pi that starts in herdr runs one. A sweep reads the launcher's pid from the run directory's name and the pane child's pid from `started`, so its reading can be stale when it acts.\n- **Time.** The 60 s session deadline is one boolean that turns true. Grace periods, poll intervals, and bg_kill's 2 s escalation are orderings only.\n- **Scope.** One run, one descendant at most, and no pid reuse. The comment on `launcherExit` in `pane-child.ts` argues against pid reuse.\n\n`SubagentRun.cfg` explores 85,086 distinct states to depth 30. The check leaves out what the launcher reports and its exit code, stderr forwarding, the `pi --print` fallback, input locking and dialogs, and `herdr-agent-state.ts`, which only reports pane state to herdr. It models a sweep only as it acts on this run's directory and pane.\n\n## Known findings\n\n`DescendantShutdownHangs.cfg` shows a descendant outliving the launcher when the descendant's graceful shutdown never ends. A `session_shutdown` handler that never returns makes that happen, because Pi awaits `runtimeHost.dispose()` with no timeout. The script prints one trace. Every trace has these steps:\n\n1. The pane child leaves `runPi`, because pi exited or a signal interrupted it. That ends `watchLauncher`, the fiber that polls the launcher's pid, and its last look finds the launcher alive.\n2. The launcher exits without stopping pi's process group. SIGKILL ends it, or its release ran before the pane child wrote `started`.\n3. The pane child exits. The kernel sends SIGHUP to the group, and the descendant starts a graceful shutdown that never ends.\n4. Nothing is left to send SIGKILL, so the descendant runs on.\n\nThe launcher's release waits `EXITED_PI_GROUP_GRACE` (1 s) and then `TERM_GRACE` (1.5 s) before its SIGKILL, so bg_kill's SIGKILL 2 s after SIGTERM can land in step 2. Closing the gap would need the pane child to outlive every other process in its group. `kill(-group, 0)` cannot show whether the group holds any other process, because the pane child is in it.\n\n`ShutdownHangs.cfg` was violated until `watchLauncher` outlasted the release of pi and the spawn of pi set `forceKillAfter`. Before that, a SIGTERM to the launcher reached the pane child through the group and interrupted `runPi`, which also ended the watch. The release then waited for pi without a limit, so a SIGKILL to the launcher during its `TERM_GRACE` left pi and the pane child running. When herdr closed the pane while pi's shutdown hung, the pane child waited for pi and the launcher polled forever. `KillEndsRun` catches that path.\n\n`ForkingPiShutdownHangs.cfg` shows the same finding for pi itself when the `pi` command forks Pi. The pane child's handle is the wrapper, so the release's SIGTERM and its `forceKillAfter` SIGKILL reach only the wrapper. Pi gets only the kernel's SIGHUP when the pane child exits, as a descendant does.\n","# Verify the subagent run lifecycle\n\n`pi-subagent` runs a child Pi in a herdr pane. Four processes share one run directory: the launcher (`bin\u002Fpi-subagent.ts`), the pane child (`bin\u002Fpane-child.ts`), pi, and possibly a descendant Pi that an extension starts before pi's `session_start`. When the `pi` command is a wrapper that forks Pi, such as a Volta shim, the wrapper is a fifth. [`SubagentRun.tla`](SubagentRun.tla) models every order in which their steps and the kills can happen, and TLC checks all of them.\n\n- **Properties.** Once the launcher has exited, by any path including SIGKILL, the pane child, pi, and the descendant all exit too (`NoProcessOutlivesLauncher`). Once any kill arrives, the launcher exits (`KillEndsRun`). Once all of them have exited, the run directory and the pane are gone (`NoRunLeftBehind`). Pi never leaves the run to another Pi, and the descendant never claims it (`OnlyPiTakesTheTask`).\n- **Harm.** A subagent that outlives its launcher keeps running its task, spending tokens and editing the worktree, after its parent thinks it is dead. A launcher that a kill does not end leaves its caller waiting forever. A run directory left behind keeps `launch.json`, which holds the launcher's environment, and a pane left behind clutters the parent's workspace. A descendant that takes the task runs it without the launcher's arguments, such as its model, and pi never ends the run. A pi that leaves the run to another Pi fails it after 60 s.\n\n## What the check covers\n\nRun the check after any change to these files, or to the Pi or Effect version in `bun.lock`. Each `\\* code:` citation in the spec names one of these functions. Every model config runs the same spec, so they all cover the same code.\n\n| File under `agent\u002Fextensions\u002Fsubagents\u002F` | Functions                                                                                                                             |\n| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |\n| `bin\u002Fpi-subagent.ts`                     | `main`, `runInPane`, `observeChild`, `addObserver`, `awaitChild`, `stopPiGroup`, `closePane`, `SIGNAL_EXIT_CODES`                     |\n| `bin\u002Fpane-child.ts`                      | `runPaneChild`, `runPi`, `watchLauncher`, `launcherExit`, `abandonRun`                                                                |\n| `process-group.ts`                       | `stopProcessGroup`                                                                                                                    |\n| `herdr.ts`                               | `sweepAbandonedRuns`, `removeAbandonedRun`, `runOwnerAlive`                                                                           |\n| `index.ts`                               | `claimRun`, `deliverTask`, `finish`, `stopChildrenInheritingRun`                                                                      |\n| `launch.ts`                              | `runFiles`, `runDirectoryPrefix`, `parseRunDirectoryName`, `runFlag`, modeled as the run directory's files and name, with no citation |\n\nThe spec also cites Pi's `interactive-mode.js` (`init`, `shutdown`, `uncaughtCrash`) and Effect's `NodeChildProcessSpawner.ts` (`terminateProcessGroup`) under `node_modules\u002F`, as installed from `bun.lock`. The sync commit below has `@earendil-works\u002Fpi-coding-agent` 1.2.0 and `effect` 4.0.4.\n\nIn sync at `5e34079ec0fd2fa4976b249a81866e3533bf50d7`. Move this commit forward only after you confirm that the model describes the code above at the new commit.\n\nA change needs a model update when it adds, removes, or reorders a step that touches shared state. Shared state is the run directory and its files (`started`, `launch.json`, `claim`, `exit`), the pane, a signal, or whether a process is alive. A change to logging, report text, or stdout touches none of it.\n\n## Run the check\n\nFrom the checkout root:\n\n```bash\n.agents\u002Fskills\u002Fverify-subagent-run\u002Fscripts\u002Fcheck\n```\n\nIt needs Java 11 or newer on `PATH`, or in `JAVA`. The first run downloads TLC 1.7.4 to `${XDG_CACHE_HOME:-~\u002F.cache}\u002Fpi-setup\u002F` and checks its sha256. A run takes about 50 s. `vp run verify` does not run it.\n\nThe script does three things:\n\n1. It checks that every `\\* code:` citation in the spec still matches its file. A citation names a file, a function, and a line copied from the code.\n2. It runs three models that must hold. [`SubagentRun.cfg`](SubagentRun.cfg) is the main model. [`ShutdownHangs.cfg`](ShutdownHangs.cfg) lets pi's graceful shutdown hang. [`ForkingPi.cfg`](ForkingPi.cfg) is the main model with a `pi` command that forks Pi.\n3. It runs [`DescendantShutdownHangs.cfg`](DescendantShutdownHangs.cfg) and [`ForkingPiShutdownHangs.cfg`](ForkingPiShutdownHangs.cfg), which reproduce known findings and must be violated. See [Known findings](#known-findings).\n\nExit 0 means every citation is current and each model matched the `\\* expect:` line at the top of its config. For a violated model, the script prints the counterexample as numbered steps. Each step names the action, the file and function it models, and the variables that changed. The full TLC output is in `${TMPDIR:-\u002Ftmp}\u002Fverify-subagent-run\u002F`.\n\n## Update the model for an intended change\n\n1. List the changed hunks with `git diff` from the sync commit on the files in [What the check covers](#what-the-check-covers).\n2. For each hunk, find the actions whose citation sits in it, or whose behavior it changes. Search the spec for the function name.\n3. Edit each action to do what the new code does. Keep one action per step that another process can observe, and fold purely local work into the neighboring step. Name a new action after the code, prefix it with `L` (launcher), `C` (pane child), or `P` (Pi), add it to `LauncherStep`, `ChildStep`, or `PiStep`, and put a `\\* code:` citation above it. Add a newly cited function to the table in [What the check covers](#what-the-check-covers).\n4. Run the check.\n\nThe update is done when the check exits 0, or when it fails only on a finding you report. If the property fails after a faithful update, the change broke it. Report the counterexample. The properties, the fairness conditions, and `EnabledKills` stay as they are.\n\n## Read a failure\n\n- **`stale citation`.** The cited line changed. Open the action above the citation and compare it with the new code. If the behavior is the same, copy the new line into the citation. If the behavior changed, update the action first.\n- **`model error (TypeOK)`.** The spec gives a variable a value outside its type. Fix the spec.\n- **`TLC error`.** TLC could not parse or evaluate the spec. The script prints the error lines.\n- **`violated, expected holds`.** Walk the counterexample and open the cited code for each step. Then classify it:\n  - **Modeling error.** A step does something the code cannot do, such as reading a file the code reads later. Fix that action to match the code.\n  - **Timing artifact.** The model has no clock, so a trace can need a step to finish faster than the code can. An example is pi registering its signal handlers microseconds after it spawns. Report the durations involved instead of calling it a bug.\n  - **Real bug.** Every step is possible in the code, in that order. Report it as steps in the code's terms, such as \"the launcher reads no `started`, then the pane child writes `started`\".\n- **`holds, expected violated`.** The known finding no longer reproduces. Confirm which change fixed it, then update [Known findings](#known-findings) and the config's `\\* expect:` line.\n\nRead every result with the **statechart** skill, which says how to chart a broken PROPERTY as an invariant over settled states. `NoProcessOutlivesLauncher`, `KillEndsRun`, and `NoRunLeftBehind` are properties. In a scratch copy of the spec, `System` is the disjunction of the actions in `Fairness`, and the invariant for `NoProcessOutlivesLauncher` is `Settled \u002F\\ launcher = \"gone\" => RunProcessesGone`. No reader takes an action here, so list the kills and Pi's own steps in `readerActions`.\n\n## What the model assumes\n\n- **Process group.** Pi and the descendant stay in the pane child's process group, the group whose id is in `started`. A process that leaves it, such as a background terminal's job, is outside the model.\n- **Hangups.** The pane child is the controlling process of the pane's terminal. Closing the pane sends it SIGHUP. When it exits, the kernel sends SIGHUP to the group and herdr removes the pane.\n- **Pi's signals.** Before `InteractiveMode.init` registers its handlers, SIGTERM or SIGHUP ends Pi. After that, either signal starts a graceful shutdown. `SubagentRun.cfg` assumes every graceful shutdown finishes. The configs whose names end in `ShutdownHangs` let one never finish.\n- **Run flag.** The pane child passes `--subagent-run=\u003Crun directory>` to the `pi` command, and a wrapper passes its arguments on to Pi. A descendant has only the arguments that its extension gives it.\n- **Wrapper.** With `PiForks`, the pane child spawns a wrapper that forks Pi into the same process group and waits for it, as a Volta shim does. The wrapper handles neither SIGTERM nor SIGHUP, as the shim in the review's reproduction does not, so a signal to the wrapper alone leaves Pi running.\n- **Pi's progress.** Nothing else Pi does is assumed. It can stop at a startup prompt, work forever, or crash at any step.\n- **Kills.** SIGKILL ends a process at once. Each kill arrives at most once and at any moment. The kills are SIGTERM to the launcher (SIGINT and SIGHUP behave the same), SIGKILL to the launcher, an external close of the pane, and SIGKILL to the pane child.\n- **herdr.** Every herdr call succeeds. Once told to run the pane child in an open pane, herdr eventually starts it.\n- **Sweeps.** Some later sweep runs, because each `pi-subagent` run and each Pi that starts in herdr runs one. A sweep reads the launcher's pid from the run directory's name and the pane child's pid from `started`, so its reading can be stale when it acts.\n- **Time.** The 60 s session deadline is one boolean that turns true. Grace periods, poll intervals, and bg_kill's 2 s escalation are orderings only.\n- **Scope.** One run, one descendant at most, and no pid reuse. The comment on `launcherExit` in `pane-child.ts` argues against pid reuse.\n\n`SubagentRun.cfg` explores 85,086 distinct states to depth 30. The check leaves out what the launcher reports and its exit code, stderr forwarding, the `pi --print` fallback, input locking and dialogs, and `herdr-agent-state.ts`, which only reports pane state to herdr. It models a sweep only as it acts on this run's directory and pane.\n\n## Known findings\n\n`DescendantShutdownHangs.cfg` shows a descendant outliving the launcher when the descendant's graceful shutdown never ends. A `session_shutdown` handler that never returns makes that happen, because Pi awaits `runtimeHost.dispose()` with no timeout. The script prints one trace. Every trace has these steps:\n\n1. The pane child leaves `runPi`, because pi exited or a signal interrupted it. That ends `watchLauncher`, the fiber that polls the launcher's pid, and its last look finds the launcher alive.\n2. The launcher exits without stopping pi's process group. SIGKILL ends it, or its release ran before the pane child wrote `started`.\n3. The pane child exits. The kernel sends SIGHUP to the group, and the descendant starts a graceful shutdown that never ends.\n4. Nothing is left to send SIGKILL, so the descendant runs on.\n\nThe launcher's release waits `EXITED_PI_GROUP_GRACE` (1 s) and then `TERM_GRACE` (1.5 s) before its SIGKILL, so bg_kill's SIGKILL 2 s after SIGTERM can land in step 2. Closing the gap would need the pane child to outlive every other process in its group. `kill(-group, 0)` cannot show whether the group holds any other process, because the pane child is in it.\n\n`ShutdownHangs.cfg` was violated until `watchLauncher` outlasted the release of pi and the spawn of pi set `forceKillAfter`. Before that, a SIGTERM to the launcher reached the pane child through the group and interrupted `runPi`, which also ended the watch. The release then waited for pi without a limit, so a SIGKILL to the launcher during its `TERM_GRACE` left pi and the pane child running. When herdr closed the pane while pi's shutdown hung, the pane child waited for pi and the launcher polled forever. `KillEndsRun` catches that path.\n\n`ForkingPiShutdownHangs.cfg` shows the same finding for pi itself when the `pi` command forks Pi. The pane child's handle is the wrapper, so the release's SIGTERM and its `forceKillAfter` SIGKILL reach only the wrapper. Pi gets only the kernel's SIGHUP when the pane child exits, as a descendant does.\n",{"name":5,"description":7},"main","f7936596c1ba2e1472dadccc3f111b9d517fa96c",".agents\u002Fskills\u002Fverify-subagent-run",2,"https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup","https:\u002F\u002Fgithub.com\u002Fdrsh4dow","https:\u002F\u002Fdanielmoretti.com\u002F","Toss A Coin To Your Witcher :pirate_flag: ","2026-11-03 22:13:53",{"source":8,"skillName":5,"commands":54,"prompt":64,"zipAvailable":65},[55,58,61],{"tool":56,"command":57},"npx","npx skills add drsh4dow\u002Fpi-setup --skill verify-subagent-run",{"tool":59,"command":60},"bunx","bunx skills add drsh4dow\u002Fpi-setup --skill verify-subagent-run",{"tool":62,"command":63},"pnpm","pnpm dlx skills add drsh4dow\u002Fpi-setup --skill verify-subagent-run","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverify-subagent-run\u002FSKILL.md",true,[67,73,77,81,87,91,95],{"path":68,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":71,"sourceHtmlUrl":72},"DescendantShutdownHangs.cfg","OTHER","其他",285,"https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverify-subagent-run\u002FDescendantShutdownHangs.cfg",{"path":74,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":75,"sourceHtmlUrl":76},"ForkingPi.cfg",308,"https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverify-subagent-run\u002FForkingPi.cfg",{"path":78,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":79,"sourceHtmlUrl":80},"ForkingPiShutdownHangs.cfg",298,"https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverify-subagent-run\u002FForkingPiShutdownHangs.cfg",{"path":82,"resourceType":83,"resourceTypeDesc":84,"sizeBytes":85,"sourceHtmlUrl":86},"scripts\u002Fcheck","SCRIPT","脚本",4449,"https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverify-subagent-run\u002Fscripts\u002Fcheck",{"path":88,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":89,"sourceHtmlUrl":90},"ShutdownHangs.cfg",421,"https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverify-subagent-run\u002FShutdownHangs.cfg",{"path":92,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":93,"sourceHtmlUrl":94},"SubagentRun.cfg",309,"https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverify-subagent-run\u002FSubagentRun.cfg",{"path":96,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":97,"sourceHtmlUrl":98},"SubagentRun.tla",33523,"https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fverify-subagent-run\u002FSubagentRun.tla",[100,121,153,170,191,202],{"skillNo":101,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":106,"publishedAt":39},"01M71P4GPJMBK21BTZBKY9T8EE","changelog","Write the changelog entry for a pull request. Use when a PR changes behavior that users, operators, or integrators can notice.","https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002Fevals\u002Fharness-lessons\u002Ffixtures\u002Fchangelog-skill\u002FSKILL.md",[],[102,107,108,109,110,111,112,113,114,115,116,117,118,32,30,119,120],"users","pull","notice","changes","operators","pr","pull request","for","request","behavior","entry","write","can","changelog entry",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":137,"publishedAt":39},"01M71P4E9AZM56JBY227P2G1SC","writing-good-tickets","Write, file, or update an issue or ticket that an agent or a developer can take to one PR. Use when filing a bug or feature, updating an issue, or breaking a plan into tickets.","https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002Fagent\u002Fskills\u002Fwriting-good-tickets\u002FSKILL.md",[127,132],{"categoryNo":128,"categoryName":129,"taxonomyType":130,"taxonomyTypeDesc":131},"01JBSEEDCAT000000000000018","写作与文案","SCENARIO","使用场景",{"categoryNo":133,"categoryName":134,"taxonomyType":135,"taxonomyTypeDesc":136},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[138,139,140,141,112,142,143,144,32,145,30,146,147,119,148,149,118,150,151,152],"good","writing","tickets","writing good tickets","feature","filing","ticket","false","file","one","plan","update","bug","take","issue",{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":161,"publishedAt":39},"01M71P4C0B0DXTRRSG9RA6Y687","writing-good-prs","Write or update a pull request title and body that a reviewer can trust and own. Use when opening a PR or editing its description.","https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002Fagent\u002Fskills\u002Fwriting-good-prs\u002FSKILL.md",[159,160],{"categoryNo":128,"categoryName":129,"taxonomyType":130,"taxonomyTypeDesc":131},{"categoryNo":133,"categoryName":134,"taxonomyType":135,"taxonomyTypeDesc":136},[138,162,139,163,108,164,112,165,32,145,30,119,166,167,113,115,149,118,168,169],"prs","writing good prs","trust","editing","reviewer","title","opening a pr","body",{"skillNo":171,"name":172,"nameZh":6,"description":173,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":174,"categories":175,"keywords":178,"publishedAt":39},"01M71P49D1AG2MR6AYNPMMT34Z","writing-good-commits","Write or reword a git commit message and size the commit it describes. Use when committing, or when reshaping commits during a rebase.","https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002Fagent\u002Fskills\u002Fwriting-good-commits\u002FSKILL.md",[176,177],{"categoryNo":128,"categoryName":129,"taxonomyType":130,"taxonomyTypeDesc":131},{"categoryNo":133,"categoryName":134,"taxonomyType":135,"taxonomyTypeDesc":136},[179,138,139,180,181,182,118,183,184,185,32,145,186,187,30,188,189,190],"commits","writing good commits","message","size","git commit message","git","git commit","rebase","commit message","committing","commit","it",{"skillNo":192,"name":193,"nameZh":6,"description":194,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":195,"categories":196,"keywords":199,"publishedAt":39},"01M71P480BDE55JMPVVEBCFS7P","writing-for-agents","Writing documents for agents. Use when creating or editing skills, or modifying AGENTS.md or CLAUDE.md.","https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002Fagent\u002Fskills\u002Fwriting-for-agents\u002FSKILL.md",[197,198],{"categoryNo":128,"categoryName":129,"taxonomyType":130,"taxonomyTypeDesc":131},{"categoryNo":133,"categoryName":134,"taxonomyType":135,"taxonomyTypeDesc":136},[200,114,139,165,32,145,201,30],"writing for agents","documents",{"skillNo":203,"name":204,"nameZh":6,"description":205,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":206,"categories":207,"keywords":208,"publishedAt":39},"01M71P45SNCD7DDSA6FRVCPA17","wizard","Generate an interactive bash wizard that walks a human through steps only they can perform. Use when provisioning infrastructure, setting up credentials or CI secrets, walking an unfamiliar third-party dashboard, or running a one-off migration or cutover. Don't invoke this for steps the agent can perform itself.","https:\u002F\u002Fgithub.com\u002Fdrsh4dow\u002Fpi-setup\u002Fblob\u002Fmain\u002Fagent\u002Fskills\u002Fwizard\u002FSKILL.md",[],[204,209,210,114,211,212,213,214,32,215,145,30,216,147,217,119,218,219,220,221],"running","setting","up","ci","provisioning infrastructure","only","invoke","Bash","interactive","credentials","dashboard","generate","human"]