pi-crew 0.11.38 → 0.11.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -0
- package/dist/index.mjs +522 -518
- package/package.json +2 -2
- package/skills/real-test-pi-crew/SKILL.md +14 -1
- package/src/config/env-allowlist-registry.ts +14 -1
- package/src/config/env-vars.ts +12 -0
- package/src/extension/registration/lifecycle-handlers.ts +12 -0
- package/src/extension/team-tool/doctor.ts +125 -1
- package/src/runtime/child-pi/child-pi-kill.ts +68 -2
- package/src/runtime/child-pi/child-pi-spawn.ts +4 -0
- package/src/runtime/heartbeat/worker-heartbeat.ts +4 -0
- package/src/runtime/process/orphan-reaper.ts +197 -22
- package/src/runtime/process/proc-stat.ts +8 -0
- package/src/runtime/process/reap-group.ts +516 -0
- package/src/runtime/process/run-descendant-sweep.ts +766 -0
- package/src/runtime/recovery/crash-recovery.ts +202 -0
- package/src/runtime/rpc/frame-client.ts +21 -1
- package/src/runtime/stale-reconciler.ts +343 -2
- package/src/runtime/task-runner/child-executor.ts +12 -1
- package/src/runtime/task-runner/state-helpers.ts +28 -1
- package/src/state/stores/state-store.ts +8 -0
- package/src/state/types.ts +42 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,109 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.11.40] — fix wave 12: fixture teardown — test-harness orphan leaks (2026-10-24)
|
|
6
|
+
|
|
7
|
+
Closes the leak class wave 11 exposed but did not own: the reaping wave made production descendants provable, and
|
|
8
|
+
then two fixtures of the shape `node -e "process.on('SIGTERM',()=>{}); setInterval(...)"` were found alive **> 6 h**
|
|
9
|
+
after their run completed, re-parented to systemd. Both were tests with `finally { child.kill() }` teardown — a
|
|
10
|
+
pid-only signal that cannot reach a tree, is a no-op against a SIGTERM-swallower, and never runs at all when the
|
|
11
|
+
spawn happens after the throw (SPEC `fixwave12-fixture-teardown-2026-10-24`).
|
|
12
|
+
|
|
13
|
+
**Added — `test/helpers/fixture-process.ts` (the fixture counterpart of `src/runtime/process/reap-group.ts`).**
|
|
14
|
+
- `spawnFixture` / `reapFixture` / `registerFixtureTrap`: pgid-correct fixture lifecycle. `spawnFixture` snapshots
|
|
15
|
+
identity (`readProcIdentity`) at spawn and normalises `pgid` to `pid` (a detached child leads its own group), so
|
|
16
|
+
the later group kill is pgid-reuse-proof; `reapFixture` escalates SIGTERM → SIGKILL and never throws (it runs in
|
|
17
|
+
`finally`, where a throw would mask the failure under test); a refusal that PROVES the pid is a stranger
|
|
18
|
+
(`starttime-mismatch`, `uid-mismatch`, `not-group-member`) signals nothing rather than pid-killing a recycled pid.
|
|
19
|
+
- `spawnFixture` **refuses non-detached children** by throwing: a non-detached fixture shares the test runner's
|
|
20
|
+
group, so a group kill rooted at it would signal the runner, sibling test files and the harness.
|
|
21
|
+
- `registerFixtureTrap` flushes registered handles **synchronously** on `exit`/`SIGINT`/`SIGTERM`, covering the abort
|
|
22
|
+
window where no `finally` runs.
|
|
23
|
+
|
|
24
|
+
**Fixed — HIGH orphan leak (`test/integration/background-runner-sigint-cleanup.test.ts`).** The grandchild group is
|
|
25
|
+
now reaped even when an assertion throws; the harness stays pid-only by design (documented in
|
|
26
|
+
`test/helpers/README.md`).
|
|
27
|
+
|
|
28
|
+
**Fixed — orphan risk in three more fixtures.** `test/unit/retrieval-orchestrator.test.ts` (the fake `rg` writes its
|
|
29
|
+
pid/pgid and the group is reaped), `test/unit/runtime/child-pi/inference-broker.test.ts` (the live `pi` is spawned
|
|
30
|
+
detached and its group reaped via `reapFixture`; the 90 s timer is demoted to a safety net, not the teardown), and
|
|
31
|
+
`test/unit/runtime/process/reap-group.test.ts` (the `spawnTree` failure path reaps its own group — GAP-4).
|
|
32
|
+
|
|
33
|
+
**Fixed — teardown race (`test/unit/extension/team-tool-steer-clamp.test.ts`).** `ENOTEMPTY` on `afterEach`: the
|
|
34
|
+
fire-and-forget `appendEventAsync` re-creates the run directory after a plain `rmSync`. Now uses
|
|
35
|
+
`removeDirWithRetry` (the existing helper) so the retry absorbs the async writer.
|
|
36
|
+
|
|
37
|
+
**Added — `getuid?: () => number | undefined` seam** on `ReapGroupOptions`/`SweepOptions`, propagated to the sweep's
|
|
38
|
+
`reapGroup` call site so discovery and group kill can never disagree about the uid (`process.getuid` is undefined on
|
|
39
|
+
win32); this also removes 3 duplicated `process.getuid` monkeypatches from tests.
|
|
40
|
+
|
|
41
|
+
**Tests.** `test/unit/runtime/process/getuid-seam.test.ts` (10 pins), `test/unit/helpers/fixture-process.test.ts`
|
|
42
|
+
(5 pins: h1–h5), `test/helpers/README.md` (fixture lifecycle rules: detached ⇒ group reap with the spawn-time
|
|
43
|
+
identity snapshot, non-detached ⇒ pid-only with the reason in a comment, never read identity in `finally`).
|
|
44
|
+
|
|
45
|
+
**Fixed — shard regression.** `helpers: 180` weight registered in `scripts/test-runner.mjs` (wave 12a created
|
|
46
|
+
`test/unit/helpers/`, which had no shard weight).
|
|
47
|
+
|
|
48
|
+
**Docs.** real-test skill: wave-12 decision-table row, history entry, failure symptom, coverage row, and the
|
|
49
|
+
wave-12d lesson (teardown of a directory with async writers must retry).
|
|
50
|
+
|
|
51
|
+
**Battery (PASS).** 30/30 targeted runs, `test:critical` 124/124, full unit 10,009 pass / 2 load-flakes (adjudicated
|
|
52
|
+
isolated ×3), integration 144/0, static gates 0, dist MATCH.
|
|
53
|
+
|
|
54
|
+
## [0.11.39] — fix wave 11: worker descendant reaping (2026-10-24)
|
|
55
|
+
|
|
56
|
+
Closes the leak that motivated the wave: run `team_20261022110510_cefb9164af126460` reached `completed` on
|
|
57
|
+
2026-10-22 and six grandchild processes (`dpi-backend` ×5 + `caddy`, ~390 MB RSS) were still alive 24 h later.
|
|
58
|
+
Root cause (SPEC `fixwave11-worker-reaping-2026-10-24` GAP-0): pi's bash tool spawns EVERY command with
|
|
59
|
+
`detached: true`, so a worker's bash grandchildren own a process group that a group kill rooted at the worker can
|
|
60
|
+
never reach.
|
|
61
|
+
|
|
62
|
+
**11a — identity-proven group kill (F1–F4).**
|
|
63
|
+
|
|
64
|
+
- `src/runtime/process/reap-group.ts` is now the ONE place that may signal a process group: a refusal ladder
|
|
65
|
+
(POSIX only, `pgid > 1`, never our own real pgrp, live-identity-or-leader-proof, same uid, member of the requested
|
|
66
|
+
group, starttime present + matching) plus a bounded SIGTERM → SIGKILL escalation. SIGTERM is delivered
|
|
67
|
+
synchronously; the SIGKILL rung is handed back as `pending` and owned by `drainReapEscalations()`
|
|
68
|
+
(stale reconciler) or `flushReapEscalationsSync()` (shutdown), never dropped.
|
|
69
|
+
- `child-pi-kill.ts` (F1): an exited ROOT no longer means "nothing left" — when the snapshot proves the root led its
|
|
70
|
+
own group, the group is reaped.
|
|
71
|
+
- `orphan-reaper.ts` (F2): recorded workers are reaped through the guarded group ladder when a pgid is known,
|
|
72
|
+
identity-proven candidates bypass the zombie-scan gate, and a scanner throw is now fail-open for proven candidates
|
|
73
|
+
instead of a blanket no-op.
|
|
74
|
+
- `state/types.ts` + `child-executor.ts` (F3): `pgid`/`starttime`/`uid` are persisted on the checkpoint and worker
|
|
75
|
+
heartbeat (all optional — old records load unchanged), which is what makes identity proof real on the next run.
|
|
76
|
+
- `frame-client.ts` (F4): the prototype RPC frame client spawns detached. The production RPC path uses the SDK
|
|
77
|
+
`RpcClient`, which exposes no spawn knob — those workers stay non-leaders.
|
|
78
|
+
|
|
79
|
+
**11b — run-scoped descendant sweep (F7, the actual cure for GAP-0).**
|
|
80
|
+
|
|
81
|
+
- New `src/runtime/process/run-descendant-sweep.ts`: `collectRunDescendants()` / `reapRunDescendants()` enumerate
|
|
82
|
+
`/proc`, match the pi-crew env markers a descendant INHERITED, and reap only through a group that can be proven.
|
|
83
|
+
Markers used (verified in src, none invented): `PI_CREW_BROKER_RUN_ID` (`child-pi-spawn.ts:382`) ⇒ `run-id`;
|
|
84
|
+
`PI_CREW_KIND=subagent` + `PI_CREW_TASK_ID` + `PI_CREW_PARENT_PID` in the recorded set ⇒ `task-id`;
|
|
85
|
+
a `PI_CREW_PARENT_PID` that is a recorded worker pid and is dead ⇒ `parent-pid`. `PI_CREW_RUN_ID` is deliberately
|
|
86
|
+
NOT matched (registry: `test-only`, no src writer).
|
|
87
|
+
- Guards: Linux `/proc` only (else `[]`, fail-open); never self, never an ancestor, same uid only; the candidate must
|
|
88
|
+
have started no earlier than the run (> 60 s of pre-run clock skew rejected); the candidate's group must not be ours;
|
|
89
|
+
one signal per group per leader incarnation (idempotent across reconciler ticks); and an extra rung — a group whose
|
|
90
|
+
LEADER is alive and carries no marker is skipped (`leader-alive-unmatched`), so a live session's group is never hit.
|
|
91
|
+
- Call sites: `stale-reconciler.ts` sweeps ONLY when the run is terminal, the process is not a worker
|
|
92
|
+
(`PI_CREW_KIND`/crew depth 0), and every recorded runner/worker pid is dead; `team-tool/doctor.ts` (`focus=zombies`)
|
|
93
|
+
reports the survivors as `runDescendants` — report-only, `dryRun: true`, nothing killed.
|
|
94
|
+
- Kill switches: `PI_CREW_REAP_GROUP` (11a) and `PI_CREW_REAP_RUN_DESCENDANTS` (11b), both boolean, default ON, both
|
|
95
|
+
forwarded to the detached background runner (never to a worker) so a disable actually takes effect where the reaping
|
|
96
|
+
runs.
|
|
97
|
+
|
|
98
|
+
**Known limitations** (unchanged by design, documented on the module and in the SPEC): a descendant that calls
|
|
99
|
+
`setsid()` or runs inside its own container/init (`docker`, `systemd-run`) has a group we cannot prove and is left
|
|
100
|
+
alone; pi's bash tool remains the primary source of detached grandchildren — the upstream fix (tracked, non-detached
|
|
101
|
+
children for tool commands, or a cgroup/subreaper for the worker tree) is what would make this sweep unnecessary;
|
|
102
|
+
and the sweep can only match processes that still carry the markers (a scrubbed env is invisible).
|
|
103
|
+
|
|
104
|
+
**11c — sync-run descent proof + test pin (leader-approved options (a) + F1 fix).**
|
|
105
|
+
|
|
106
|
+
- Record sync-run `manifest.sessionPid` so foreground-run leaks satisfy the descent proof (wave 11c): a sync run has no `async.pid`, so a leaked worker grandchild whose `PI_CREW_PARENT_PID` points at the session pi itself could never be proven and was skipped forever; `sessionPid` is recorded at creation and treated as a recorded parent (dead-only, like the others). Also pin `PI_CREW_REAP_RUN_DESCENDANTS` in the sweep suite's setup so the tests no longer depend on the ambient kill-switch state. Battery addendum: `docs/real-test/reports/real-test-2026-10-24-wave11-battery-addendum.md` (F1/F2 closed, verdict PASS).
|
|
107
|
+
|
|
5
108
|
## [0.11.38] — Wave 10: pi-subagents lessons (10 ports) + watchdog hung-alert hardening (WD-DEDUP / WD-STALE-RECORD / WD-DELIVER-GUARD)
|
|
6
109
|
|
|
7
110
|
### Fixed — watchdog alerts for FINISHED runs: delivery-time guard (WD-DELIVER-GUARD, root-caused 2026-10-23)
|