pi-crew 0.6.1 → 0.6.4

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.
Files changed (119) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +81 -33
  3. package/docs/issue-29-analysis.md +189 -0
  4. package/docs/superpowers/plans/2026-06-09-fallow-patterns-adoption.md +1268 -0
  5. package/docs/ui-optimization-plan.md +447 -0
  6. package/package.json +2 -2
  7. package/src/config/config.ts +106 -15
  8. package/src/errors.ts +107 -0
  9. package/src/extension/async-notifier.ts +6 -2
  10. package/src/extension/crew-cleanup.ts +8 -5
  11. package/src/extension/management.ts +464 -109
  12. package/src/extension/register.ts +213 -35
  13. package/src/extension/registration/brief-tool-overrides.ts +400 -0
  14. package/src/extension/registration/commands.ts +27 -2
  15. package/src/extension/registration/subagent-helpers.ts +2 -2
  16. package/src/extension/registration/subagent-tools.ts +9 -4
  17. package/src/extension/registration/team-tool.ts +17 -10
  18. package/src/extension/registration/viewers.ts +2 -2
  19. package/src/extension/team-tool/api.ts +3 -3
  20. package/src/extension/team-tool/cancel.ts +3 -3
  21. package/src/extension/team-tool/explain.ts +1 -1
  22. package/src/extension/team-tool/handle-schedule.ts +40 -0
  23. package/src/extension/team-tool/inspect.ts +3 -3
  24. package/src/extension/team-tool/lifecycle-actions.ts +4 -4
  25. package/src/extension/team-tool/respond.ts +2 -2
  26. package/src/extension/team-tool/run.ts +64 -14
  27. package/src/extension/team-tool/status.ts +1 -1
  28. package/src/extension/team-tool-types.ts +2 -0
  29. package/src/extension/team-tool.ts +173 -46
  30. package/src/hooks/registry.ts +77 -13
  31. package/src/hooks/types.ts +9 -0
  32. package/src/plugins/plugin-define.ts +6 -0
  33. package/src/plugins/plugin-registry.ts +32 -0
  34. package/src/plugins/plugins/index.ts +3 -0
  35. package/src/plugins/plugins/nextjs.ts +19 -0
  36. package/src/plugins/plugins/vite.ts +14 -0
  37. package/src/plugins/plugins/vitest.ts +14 -0
  38. package/src/runtime/async-runner.ts +35 -7
  39. package/src/runtime/background-runner.ts +529 -144
  40. package/src/runtime/chain-parser.ts +5 -1
  41. package/src/runtime/chain-runner.ts +9 -3
  42. package/src/runtime/checkpoint.ts +262 -180
  43. package/src/runtime/child-pi.ts +164 -37
  44. package/src/runtime/crash-recovery.ts +25 -14
  45. package/src/runtime/crew-agent-records.ts +2 -0
  46. package/src/runtime/diagnostic-export.ts +1 -1
  47. package/src/runtime/dynamic-script-runner.ts +10 -7
  48. package/src/runtime/foreground-watchdog.ts +1 -1
  49. package/src/runtime/heartbeat-watcher.ts +19 -2
  50. package/src/runtime/intercom-bridge.ts +7 -0
  51. package/src/runtime/iteration-hooks.ts +1 -1
  52. package/src/runtime/manifest-cache.ts +4 -0
  53. package/src/runtime/orphan-worker-registry.ts +444 -0
  54. package/src/runtime/parent-guard.ts +70 -16
  55. package/src/runtime/pi-args.ts +437 -14
  56. package/src/runtime/pi-spawn.ts +1 -0
  57. package/src/runtime/post-checks.ts +1 -1
  58. package/src/runtime/retry-runner.ts +9 -3
  59. package/src/runtime/run-tracker.ts +38 -10
  60. package/src/runtime/sandbox.ts +17 -2
  61. package/src/runtime/scheduler.ts +25 -2
  62. package/src/runtime/skill-effectiveness.ts +105 -62
  63. package/src/runtime/skill-instructions.ts +110 -32
  64. package/src/runtime/stale-reconciler.ts +310 -69
  65. package/src/runtime/subagent-manager.ts +251 -57
  66. package/src/runtime/task-health.ts +76 -0
  67. package/src/runtime/task-id.ts +20 -0
  68. package/src/runtime/task-runner/live-executor.ts +1 -1
  69. package/src/runtime/task-runner/progress.ts +1 -1
  70. package/src/runtime/task-runner/state-helpers.ts +110 -6
  71. package/src/runtime/task-runner.ts +92 -70
  72. package/src/runtime/team-runner.ts +186 -20
  73. package/src/schema/team-tool-schema.ts +27 -9
  74. package/src/skills/discover-skills.ts +9 -3
  75. package/src/state/active-run-registry.ts +170 -38
  76. package/src/state/artifact-store.ts +25 -13
  77. package/src/state/atomic-write-v2.ts +86 -0
  78. package/src/state/atomic-write.ts +346 -55
  79. package/src/state/blob-store.ts +178 -10
  80. package/src/state/crew-init.ts +161 -28
  81. package/src/state/decision-ledger.ts +172 -111
  82. package/src/state/event-log-rotation.ts +82 -52
  83. package/src/state/event-log.ts +254 -70
  84. package/src/state/health-store.ts +71 -0
  85. package/src/state/locks.ts +102 -20
  86. package/src/state/mailbox.ts +45 -7
  87. package/src/state/observation-store.ts +4 -1
  88. package/src/state/run-graph.ts +141 -130
  89. package/src/state/run-metrics.ts +24 -8
  90. package/src/state/session-state-map.ts +51 -0
  91. package/src/state/state-store.ts +330 -43
  92. package/src/ui/live-run-sidebar.ts +1 -1
  93. package/src/ui/loaders.ts +4 -0
  94. package/src/ui/overlays/agent-picker-overlay.ts +1 -1
  95. package/src/ui/overlays/mailbox-detail-overlay.ts +1 -1
  96. package/src/ui/powerbar-publisher.ts +1 -1
  97. package/src/ui/run-action-dispatcher.ts +2 -2
  98. package/src/ui/run-snapshot-cache.ts +1 -1
  99. package/src/ui/status-colors.ts +5 -1
  100. package/src/ui/theme-adapter.ts +80 -1
  101. package/src/ui/tool-progress-formatter.ts +9 -5
  102. package/src/ui/tool-render.ts +4 -0
  103. package/src/ui/tool-renderers/brief-mode.ts +207 -0
  104. package/src/ui/tool-renderers/index.ts +627 -0
  105. package/src/ui/widget/index.ts +224 -0
  106. package/src/ui/widget/widget-formatters.ts +148 -0
  107. package/src/ui/widget/widget-model.ts +90 -0
  108. package/src/ui/widget/widget-renderer.ts +130 -0
  109. package/src/ui/widget/widget-types.ts +37 -0
  110. package/src/utils/env-filter.ts +66 -0
  111. package/src/utils/file-coalescer.ts +9 -1
  112. package/src/utils/guards.ts +110 -0
  113. package/src/utils/paths.ts +80 -5
  114. package/src/utils/redaction.ts +6 -1
  115. package/src/utils/safe-paths.ts +281 -10
  116. package/src/worktree/cleanup.ts +112 -24
  117. package/src/worktree/worktree-manager.ts +128 -15
  118. package/test-bugs-all.mjs +10 -6
  119. package/test-integration-check.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,199 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.4] — Visually Rich Tool Rendering: Merged Frames, Live Progress Bars (2026-06-14)
4
+
5
+ ### Highlights
6
+ - **Visually rich team & agent tool rendering** — framed cards with box-drawing borders, colored status badges, and structured layouts for `team` and `Agent` tool calls in the Pi TUI
7
+ - **Merged call+result into ONE connected frame** — previously `renderCall` and `renderResult` each drew a complete box, producing two disconnected frames. Now they split a single frame (top border + header from `renderCall`, content + bottom border from `renderResult`) that merge seamlessly in Pi's `Box(1,1)` container
8
+ - **Animated live progress bar during runs** — real-time task progress (`tasks completed=N/M`) parsed from streaming updates and rendered as a `████░░░░ N/M` bar with elapsed time, DURING the run (not after completion). Indeterminate "starting" phase uses an animated scanning bar
9
+ - **Compact summary after completion** — collapsed cards show `✓ crew run 3/3 done · 1m2s · 26k tok · $0.068` with expand hint (`⌘E`) and agent briefs (`✓ explorer · 45.0s · 8.0k tok`)
10
+ - **Crash fix on session resume** — `renderCall` was returning a `string` (from `buildFrame`), causing `TypeError: child.render is not a function` when Pi re-rendered stored tool calls on resume. Now wraps in `new Text(...)`
11
+
12
+ ### Bug Fixes
13
+ - **`5613ecc`** — **Critical crash fix**: `teamToolRenderer.renderCall` and `agentToolRenderer.renderCall` returned `buildFrame(...)` (a string), not a Component. Pi's `addChild(string)` stored the string in `children[]`, then `Box.render()` called `child.render(width)` on the string → crash. Only surfaced on resume because fast-completing tools got their `Text` result frame painted before the string call frame was rendered. Fixed by wrapping both renderers in `new Text(..., 0, 0)`.
14
+ - **`58ba6e5`** — Elapsed time miscalculation: Pi's `ctx.executionStarted` is a **boolean** flag (not a timestamp), so `Date.now() - true` produced ~56-year durations. Now timing is tracked via `ctx.state.briefStartedAt`.
15
+ - **`1c2cf71`** — Reverted `lastComponent` reuse: returning `ctx.lastComponent` and mutating its private `.text` field crashed on session resume (deserialized components lose prototype methods). Pi already calls `renderContainer.clear()` before each `updateDisplay()`, so single-frame streaming is guaranteed without reuse.
16
+ - **`7d01ebb`** — Typecheck fix: `agentToolRenderer.renderCall` had parameter named `_ctx` (unused convention) but `borderFromContext(ctx)` referenced `ctx` (`error TS2552`).
17
+
18
+ ### Reverts
19
+ - **`0763e67`** — **Disabled brief tool overrides**. Re-registering built-in tools (read/bash/edit/write/find/grep/ls) replaced Pi's superior native renderers (syntax highlighting, diff views, full file content) with inferior custom `fullRender()` output, and caused `renderCall`/`renderResult` to duplicate path/command info. The file is retained for reference; re-enable by uncommenting one line in `register.ts`.
20
+
21
+ ### Test Fixes
22
+ - **`39d1dc7`** — `AnimatedMascot` timing tests were flaky under CI load. The animation advances via `setInterval(20ms)` which is `unref()`'d; under `--test-concurrency=4` the unref'd timers get delayed, so a fixed 70ms wait wasn't always enough for one tick. Replaced fixed waits with polling loops (retry until the frame advances, up to 600ms). Applied to both cat and armin animation tests. Robust: finishes fast normally (~40ms), tolerates heavy load.
23
+
24
+ ### Features (UI)
25
+ - **`a7b703b`** — `parseStreamingProgress()` parses `tasks completed=N running=M` and `N/M done` formats from streaming progress text; `renderScanBar()` renders an animated bouncing bar for the indeterminate "starting" phase.
26
+ - **`9b1de38`** — `onRunStarted` now called in the async path of `run.ts` (was only in foreground path), so background runs attach the progress binder and show real-time progress instead of stuck "starting".
27
+ - **`5741d73`** — `formatCompactToolProgress` always includes the `tasks N/M done status=X` line even when an active agent is present (was skipped via `else if` bug).
28
+ - **`22d8132`** — `extractContentText` returns only the LAST text block (was `.join("\n")` on all blocks, causing stacked progress frames during streaming).
29
+ - **`3777fbc`** — `buildFrameTop()` / `buildFrameBottom()` split rendering so `renderCall` + `renderResult` merge into one connected frame; `borderFromContext(ctx)` keeps top and bottom border colors consistent (accent while running, green on success, red on error).
30
+ - **`9fa5153`** — Cost display in collapsed cards (`computeTotalCost()`), `⌘E` expand hint, agent briefs with duration/tokens, `shortenPath()` (`$HOME` → `~`), OSC 8 clickable paths (`linkPath()`).
31
+ - **`f9c9803`** — Frame width auto-adjusts to terminal via `process.stdout.columns`.
32
+
33
+ ### Stats
34
+ - 9 commits since v0.6.3
35
+ - CI green on Ubuntu, macOS, and Windows
36
+
37
+ ## [0.6.3] — Cross-Platform CI, 87 Test Fixes, Worktree Validation, Heartbeat & Crash Fixes (2026-06-12)
38
+
39
+ ### Highlights
40
+ - **Cross-platform CI green** — 0 failures across Ubuntu, macOS, and Windows (4,725 unit + 113 integration tests)
41
+ - **87 pre-existing test failures resolved** — 0 failures across 4,792 tests in 506 test files
42
+ - **Heartbeat false-positive dead detection fixed** — `message_start` added to progress flush events; PID liveness gate uses `task.checkpoint.childPid` fallback
43
+ - **ENOENT crash on prune/forget race fixed** — 4-layer defense in `persistSingleTaskUpdate`, `persistHeartbeat`, `saveRunTasks`, and `upsertCrewAgent`
44
+ - **Scheduled job lifecycle completed** — spawned runs tracked via `spawnedRunIds[]`, auto-cancelled on job removal, manifests stamped with `schedulerJobId` for traceability
45
+ - **Worktree precondition validation** — friendly error messages instead of crashes when cwd is not a git repo or repo has uncommitted changes
46
+ - **Cross-platform path handling** — `canonicalizePath` with `realpathSync.native` for Windows short-name/long-name aliasing; macOS `/var` → `/private/var` symlink resolution
47
+ - **Pipe buffer deadlock fix** — `spawnSync` with `stdio: 'pipe'` caused deadlock when OS pipe buffer (~64KB) filled at ~227 tests; switched to `stdio: 'inherit'`
48
+ - **Stale lock recovery** — removed `readLockToken` guard so stale locks without 'token' field are properly deleted
49
+ - **Full-feature smoke test** — 58 integration tests covering all pi-crew actions
50
+ - **Pre-push review**: 56 unpushed commits reviewed (116 files, +9,599/−980 lines), 1 release blocker found and fixed
51
+
52
+ ### Bug Fixes
53
+ - **`89ed975`** — Heartbeat watcher: added `message_start` to `shouldFlushProgressEvent()` so LLM stream start updates `lastActivityAt`. Previously, an 8m53s LLM response (365 file reads, no tool calls) triggered false `heartbeat_dead` at 300s threshold.
54
+ - **`9c1bf1f`** — Heartbeat watcher: PID liveness gate uses `task.heartbeat?.pid ?? task.checkpoint?.childPid` fallback. Review team discovered the gate was dead code because `createWorkerHeartbeat(taskId)` never receives PID.
55
+ - **`2bbbb99`** — ENOENT crash: `persistSingleTaskUpdate` recheck stat wrapped in try/catch; `persistHeartbeat` catches ENOENT; `saveRunTasks` guards with `statSync(stateRoot)`; `upsertCrewAgent` skips if stateRoot gone.
56
+ - **`08df7ce`** — Release blocker: `src/errors.ts` enum→const object, `src/state/health-store.ts` parameter property — both incompatible with Node 22 `--experimental-strip-types`.
57
+ - **`3e0b957`** — Sandbox constructor escape detection strengthened.
58
+ - **`dd279bc`** — EBADF (missing O_WRONLY flag), re-entrant sync locks, worktree list parsing, env-filter provider keys.
59
+ - **`38b8f5a`** — Create `transcripts/` directory before child-pi appends.
60
+ - **`d893434`** — Child-pi: remove API key allowlist; child Pi uses same config as parent.
61
+ - **`5cb9122`** — Cross-platform: `canonicalizePath` in `paths.ts` uses `realpathSync.native` for consistent long-name paths on Windows; all test temp dirs canonicalized through `.native`.
62
+ - **`3b46556`** — Cross-platform: `resolvedWorktreeRoot` uses `.native` for git worktree compatibility on Windows; worktree list comparison normalizes through `.native`.
63
+ - **`b2b7068`** — Worktree: precondition validation in `team-tool/run.ts` checks git repo existence and clean leader status before creating run manifest, returning friendly error messages instead of crashing.
64
+ - **`8090fe2`** — Worktree: respect `requireCleanWorktreeLeader` config setting in precondition check.
65
+ - **`2014739`** — Pipe buffer deadlock: `spawnSync` with `stdio: 'pipe'` caused deadlock when OS pipe buffer (~64KB) filled at ~227 tests; switched to `stdio: 'inherit'`.
66
+ - **`d6920bf`** — Stale lock recovery: removed `readLockToken` guard so stale locks without 'token' field are properly deleted.
67
+ - **`3897c1d`** — Mailbox: return paths as-is from `safeMailboxDir`, `safeMailboxFile`, `safeMailboxTasksRoot`, `taskMailboxDir` instead of re-resolving through `resolveRealContainedPath` which changed path forms on Windows.
68
+ - **`f867d4d`** — macOS: `realpathSync(os.tmpdir())` in 3 test files to handle `/var` → `/private/var` symlink.
69
+ - **`2fd0c1e`** — TypeScript: fix Property 'text' and Property 'taskId'→'id' errors for strict compiler checks.
70
+
71
+ ### Test Fixes (87 total)
72
+ - **`1ab7926`** — 33 failures: state-store mtime CAS, locks race, discovery, atomic-write, config-schema, blob-store, env-filter, sandbox, security-hardening, worktree
73
+ - **`bba0bed`** — 3 failures: blob dedup, auto-recovery cap, transcript append
74
+ - **`03dd9b3`** — 14 failures: team-runner, retry-runner, hooks, stale-reconciler, resume-checkpoint, dynamic-script-runner, adaptive-implementation
75
+ - **`a91c316`** — 5 failures: re-entrant sync locks (`withRunLockSync`), `registerWorker()` optional `registeredAt`, `phase8-smoke` PI_TEAMS_HOME isolation, `test-integration-check` PI_CREW_ALLOW_MOCK, `test-bugs-all.mjs` graceful skip
76
+ - **`952c14d`** — 58 full-feature smoke tests added
77
+
78
+ ### Features
79
+ - **`14269f0`** — Scheduler tracks spawned runs: `ScheduledJob.spawnedRunIds[]`, `CrewScheduler.recordSpawnedRun()`, `remove()` calls `runCancelFn` per spawned run, manifests stamped with `schedulerJobId`/`schedulerName`.
80
+ - **`e499570`** — Plugin registry system for framework context injection (Next.js, Vite, Vitest).
81
+ - **`84170c3`** — Team runner integrates plugin registry for framework-aware task context.
82
+ - **`ee466a8`** — Health score system with penalty-based scoring and time-series snapshots.
83
+ - **`daa53ab`** — Atomic write v2 with fsync + rename pattern for crash-safe state persistence.
84
+ - **`6c01f2c`** — CrewError taxonomy: E001–E006 structured error codes.
85
+ - **`2ce143f`** — State-store uses CrewError for structured errors.
86
+ - **`0cd4853`** — Stable task IDs via `stableIdFromContent` for cross-run consistency.
87
+ - **`ff3da92`** — Health snapshot saved on run completion.
88
+
89
+ ### Stats
90
+ - 137 commits since v0.6.1
91
+ - 200 files changed (+16,955 / −2,057 lines)
92
+ - 366 source files, ~70K lines TypeScript
93
+ - 506 test files, ~66K lines TypeScript
94
+ - 4,792 tests, 0 failures
95
+ - CI: Ubuntu ✅ macOS ✅ Windows ✅ (0 failures each)
96
+
97
+
98
+ ## [0.6.3] — Post-Release Hardening: Cleanup, Safe-Paths, State-Store Race (2026-06-08)
99
+
100
+ ### Highlights
101
+ - **State-store manifest/tasks mtime race fixed** (commits `04fe0be`, `f15ee98`) — `loadRunManifestById` no longer throws on benign mtime skew between `manifest.json` and `tasks.json`. A previous user review (run `team_20260608082852_*`) hit a 4812-second hang because of this throw; the fix prevents the same hang from recurring.
102
+ - **Orphan worker + temp dir cleanup hardened** (8 commits) — 4-layer defense (in-memory Set, per-session temp dir, user-root temp dir, legacy `/tmp` cleanup) with symlink guards, `O_NOFOLLOW` opens, and bounded batch sizes.
103
+ - **`PI_CREW_PARENT_PID` restored to child env allow-list** (commit `e1f7dfe`) — silent regression from a previous round fixed; parent-guard now works again for orphan-worker detection.
104
+ - **`safe-paths.resolveRealContainedPath` extended** (commits `ba0ce54`, `aa457a5`) — now supports creating new files (target does not have to exist) while keeping full symlink-ancestor protection.
105
+ - **`blob-store` metadata race fixed** (commit `5819b18`) — per-hash in-memory lock + atomic write of content-then-metadata prevents concurrent writers from corrupting metadata.
106
+ - **Behavior change: `parent-guard` no longer `.unref()`s its interval** — the guard timer now keeps the event loop alive by design so workers do not exit while their parent is still alive but the worker has no other pending work. See "Behavior Changes" below.
107
+
108
+ ### Security Fixes
109
+ - **`e1f7dfe`** — Restored `PI_CREW_PARENT_PID` in `child-pi.ts` env allow-list. Previous round (`dbf7a48`) replaced `PI_CREW_*`/`PI_TEAMS_*` wildcards with an explicit list but omitted `PI_CREW_PARENT_PID`, silently breaking the parent-guard mechanism.
110
+ - **`ba0ce54` / `aa457a5`** — `safe-paths.resolveRealContainedPath` now allows new-file creation while keeping symlink-ancestor protection. Documented the asymmetric ancestor policy in the function JSDoc.
111
+ - **`e1f7dfe`** (sibling) — `worktree/cleanup.ts` no longer uses `PI_*` / `PI_CREW_*` wildcards in `GIT_SAFE_ENV` (could match secret vars like `PI_PASSWORD`).
112
+ - **`2b8f27a` / `1bf67eb`** — Child env allow-list switched from dangerous wildcards (`*_API_KEY`, `*_TOKEN`, `*_SECRET`, `LC_*`, `XDG_*`, `NPM_*`) to an explicit list of 6 API keys + 12 essential env vars. Eliminates accidental secret leakage via matching name patterns.
113
+
114
+ ### Cleanup Hardening (8 commits)
115
+ - **`5edcb18`** — Track temp dirs globally via in-memory `Set<string>`, cap `reconcileOrphanedTempWorkspaces` scan size, fix cleanup stub.
116
+ - **`8ba270d`** — Move temp dirs from `/tmp` to `~/.pi/agent/pi-crew/tmp/` (uses `userPiRoot()` so `PI_TEAMS_HOME` / `PI_CODING_AGENT_DIR` are respected). Eliminates `/tmp` pollution and unifies state layout.
117
+ - **`ceb1cb1`** — Layer 4 periodic cleanup for orphan prompt/task temp dirs older than 24h.
118
+ - **`a76932d`** — Skip symlinks and in-use dirs during cleanup, plus a one-shot legacy `/tmp` sweep to clean up directories left behind by pre-`8ba270d` installations.
119
+ - **`c9eb430`** — Kill orphan background workers and trigger temp cleanup on `session_start`.
120
+ - **`a192509`** — 4 critical hardening fixes: never `rmSync` a symlink, double-check immediately before delete, skip dirs currently in use, and tear down the global tracker on process exit.
121
+ - **`992231d`** — 24 new unit tests in `test/unit/cleanup-orphan-temp.test.ts` covering each cleanup layer and failure mode.
122
+ - **`dbf7a48`** — UI: replace `console.log` cleanup messages with `notifyOperator` for proper user notification.
123
+
124
+ ### Bug Fixes
125
+ - **`5819b18`** — `blob-store.writeBlob` race condition: per-hash lock + atomic write of content-then-metadata (previously metadata first, leaving orphans on blob write failure).
126
+ - **`04fe0be`** — `state-store.loadRunManifestById` returned `undefined` (was: threw) on manifest/tasks mtime mismatch — the throw caused background runners to crash within 1s of startup.
127
+ - **`f15ee98`** — `state-store.loadRunManifestById` removed the false-positive mtime check entirely. The `saveManifestAndTasksAtomicSync` writer intentionally writes manifest before tasks, so a manifest with newer mtime than tasks is a NORMAL post-write state, not corruption.
128
+ - **`cd7ef89` / `a0c2ba3` / `098c8a9` / `b782424` / `e1ea7d4` / `de3f550` / `2b8f27a` / `1bf67eb`** — 8 deep-review auto-fix commits addressing 78+77+29+28+24+24+24+24 verified issues across the cleanup, state, runtime, and utils modules.
129
+ - **`e1f7dfe`** — `parent-guard.ts` unref'd timers were silently causing worker exit while parent was still alive; restoring the allow-list entry brought the guard back into effect.
130
+
131
+ ### Behavior Changes
132
+ - **`parent-guard.ts` no longer `.unref()`s the guard interval** (revert/restore series `0aed8b5` / `152ac80` / `ee0ddb4` / `81b9608`). The watchdog timer now keeps the event loop alive by design. If a worker has no other pending work (no I/O, no timers, no child processes), the guard interval is the only thing keeping the worker alive until either the parent dies or the worker is explicitly stopped. The previous revert-then-restore pattern ("to test if they cause pi hang") never conclusively identified a root cause; the current state was reached after manual testing. **Mitigation recommended**: add a max-worker-lifetime safety net in a future release.
133
+
134
+ ### New / Heavily Expanded Source Files
135
+ - **`src/runtime/orphan-worker-registry.ts`** (NEW, 307 lines) — PID+start-time+parent-PID verification before SIGKILL; file-locked registry at `<userPiRoot>/state/orphan-workers.json`; honest about residual userspace TOCTOU window between start-time re-check and actual `process.kill`.
136
+ - **`src/runtime/pi-args.ts`** (heavily expanded, 342 lines) — `createSafeTempDir` walks the full ancestor chain rejecting symlinks; `buildPiWorkerArgs` builds the child argv safely (no shell); `cleanupTempDir` / `cleanupAllTrackedTempDirs` / `cleanupOrphanTempDirs` / `cleanupLegacyOrphanTempDirs` provide 4 layers of defense with bounded work.
137
+
138
+ ### Test Coverage
139
+ - **`test/unit/orphan-worker-registry.test.ts`** (NEW, 279 lines) — `registerWorker` / `unregisterWorker` / `cleanupOrphanWorkers` with `__test_setRegistryPath` for isolation; covers invalid PIDs, dedup, parent-PID tolerance, current-session protection, dead-PID pruning.
140
+ - **`test/unit/cleanup-orphan-temp.test.ts`** (NEW, 242 lines) — covers `cleanupTempDir` / `cleanupAllTrackedTempDirs` / `cleanupOrphanTempDirs` / `cleanupLegacyOrphanTempDirs` with `utimesSync` to simulate aged dirs; tests symlink skip, in-use skip, and `/tmp` legacy cleanup.
141
+ - **`test/integration/cleanup-full-flow.test.ts`** (NEW, 241 lines) — end-to-end integration of all cleanup layers, simulating a crashed session.
142
+
143
+ ### Documentation
144
+ - **`src/runtime/parent-guard.ts`** — added a "Trust model" JSDoc section explaining why `PI_CREW_PARENT_PID` is safe to pass in env (PID is not a secret), what residual risks remain (child can spoof before guard starts), and why the guard is a self-termination signal, not a security boundary.
145
+ - **`src/utils/safe-paths.ts:resolveRealContainedPath`** — added a "Security model — asymmetric ancestor handling" JSDoc section explaining why `baseDir` ancestors must exist (cannot validate otherwise) while target ancestors may be non-existent (for new-file creation).
146
+
147
+ ### Stats
148
+ - 23 commits since v0.6.2: `0aed8b5`, `152ac80`, `ee0ddb4`, `81b9608`, `5edcb18`, `8ba270d`, `ceb1cb1`, `a76932d`, `c9eb430`, `a192509`, `992231d`, `dbf7a48`, `e1f7dfe`, `1bf67eb`, `2b8f27a`, `de3f550`, `e1ea7d4`, `5819b18`, `cd7ef89`, `b782424`, `a0c2ba3`, `098c8a9`, `ba0ce54`, `aa457a5`, `04fe0be`, `f15ee98`
149
+ - 79 files changed (+3567 / -712)
150
+ - 1 new state fix: manifest/tasks mtime false positive
151
+ - 1 new file: `orphan-worker-registry.ts` (307 lines)
152
+ - 3 new test files: 762 lines
153
+
154
+ ## [0.6.2] — Issue #28 + #29 Fixes + Post-Review Hardening (2026-06-05)
155
+
156
+ ### Highlights
157
+ - **Issue #28 fixed**: `crew-init.ts` jiti namespace race — inline `parseRoot`/`safeJoin`/`safeDirname`/`safeResolve` helpers; jiti upgraded 2.6.1 → 2.7.0
158
+ - **Issue #29 fixed**: 11 hardcoded `.crew/state/runs/...` sites now use `projectCrewRoot()` for `.pi/teams/` fallback
159
+ - **Subagent defense-in-depth**: `record.promise` rejection can no longer crash pi via `unhandledRejection`
160
+ - **2 new MEDIUM-severity path-traversal vulnerabilities fixed** in `decision-ledger.ts` (7 functions) and `run-graph.ts` (3 functions)
161
+ - **F-8 safeJoin edge case** fixed (trailing separator handling)
162
+ - **~100 new tests** across 7 files (5 e2e scripts + 18 regression tests for the new fixes)
163
+
164
+ ### Security Fixes (MEDIUM)
165
+ - `decision-ledger.ts`: `initLedger`, `appendEntry`, `getLedger`, `getLatestDecision`, `summarizeLedger`, `promoteCandidate`, `decayCandidate` now call `assertSafePathId("runId", runId)` to prevent path-traversal
166
+ - `run-graph.ts`: `saveRunGraph`, `loadRunGraph`, `listRunGraphs` use `projectCrewRoot()` and `assertSafePathId("runId", runId)` (same bug class as issue #29)
167
+
168
+ ### Bug Fixes
169
+ - **F-1** (post-issue-#29 review): removed duplicate "Defense in depth" comment in `subagent-manager.ts`
170
+ - **F-3** (post-issue-#29 review): added F-6 documentation in `test/unit/crew-init.test.ts` header
171
+ - **F-8** (post-issue-#29 review): `safeJoin("/", "foo")` no longer produces `"//foo"`; trailing separator in parts is stripped (UNC paths preserved)
172
+ - **`crew-init.ts safeJoin`**: collapse internal runs of separator while preserving leading UNC `\\\\` and POSIX `/`
173
+
174
+ ### Test Coverage
175
+ - 9 new regression tests in `test/unit/issue-29-pi-paths.test.ts` (resolver precedence, `waitForRun` error message in both layouts, checkpoint/skill-effectiveness round-trip in `.pi/`-only project, decision-ledger path, defense-in-depth via real child process)
176
+ - 13 new tests in `test/unit/crew-init.test.ts` (F-1 UNC preservation, F-2 jiti race via `path` Proxy, F-3 `safeResolve` graceful degradation, F-4 `__test__internals` export convention, F-5 stale docstring, F-6 async-runner rationale, F-8 trailing-separator)
177
+ - 4 new tests in `test/unit/crew-init.test.ts` for `safeJoin` (F-8 regression coverage)
178
+ - 7 new tests in `test/unit/decision-ledger.test.ts` (path-traversal rejection)
179
+ - 3 new tests in `test/unit/run-graph.test.ts` (path-traversal rejection)
180
+ - 14 new tests in `test/unit/skill-effectiveness.test.ts` (cwd parameter migration)
181
+ - 5 new E2E scripts in `scripts/test-issue-29-*.ts`:
182
+ - `test-issue-29-e2e.ts` — unit-level integration
183
+ - `test-issue-29-crash.ts` — focused crash reproduction
184
+ - `test-issue-29-team-tool.ts` — slow-path early-exit error message
185
+ - `test-issue-29-real-tasks.ts` — full `executeTeamRun` pipeline in `.pi/`-only project (25 assertions)
186
+ - **`test-issue-29-real-runtime.ts`** — spawns REAL detached `background-runner.ts` process (most realistic)
187
+
188
+ ### Test Quality Improvements
189
+ - **F-4** (post-issue-#29 review): `crew-init.ts findProjectRoot` now accepts optional `path` dep; test passes the `stubPath` Proxy directly to source code (not a copy)
190
+ - **F-5** (post-issue-#29 review): defense-in-depth crash test now spawns a real child process so the host's `unhandledRejection` detector can actually fire (was previously a comment-only test)
191
+
192
+ ### Stats
193
+ - 7 source files changed (3 fixes + 1 new path-resolver usage)
194
+ - 7 test files changed (4 unit + 5 e2e scripts)
195
+ - 9 commits since v0.6.1: `e95e055`, `cd8c3b8`, `a80fe6c`, `362789c`, `0c78307`, `083afaf`, `d33b86c`, `03c0a20`, `1bedd24`, `0ce3d5a`, `b17fb6b`, `f8731e6`, `105d31d`, `00c66a5`, `2d49910`, `6cbbafa`
196
+
3
197
  ## [0.6.1] — Post-v0.6.0 Security Hardening + Test Coverage (2026-06-04)
4
198
 
5
199
  ### Highlights
package/README.md CHANGED
@@ -9,26 +9,39 @@ npm: pi-crew
9
9
  repo: https://github.com/baphuongna/pi-crew
10
10
  ```
11
11
 
12
- **v0.6.1**: See [CHANGELOG.md](CHANGELOG.md).
13
-
14
- ### Security highlights (v0.6.1)
15
-
16
- - **42+ security issues fixed** — 7 CRITICAL, 10 HIGH, 11 MEDIUM, 14 post-restart findings
17
- - **Timing-safe token comparison** — constant-time compare for lock tokens and request IDs
18
- - **Environment leak prevention** — `sanitizeEnvSecrets()` on all child process spawns
19
- - **Shell injection hardened** — `execFileSync` with array args; blocked `String.fromCharCode` bypass
20
- - **ReDoS-free secret redaction** — linear-time scanning in `redaction.ts`
21
- - **Sandbox prototype isolation** — `Object.freeze` scoped to VM context; `constructor` pattern blocked
22
- - **Symlink traversal prevention** — `fs.realpathSync` before path containment checks
23
- - **Safe-bash line-continuation hardening** — `$\n(evil)` command substitution bypass blocked
24
- - **Path traversal mitigated** — `resolveContainedPath`/`resolveRealContainedPath` across all file ops
25
- - **Memory leaks capped** — Maps, Sets, arrays bounded with eviction across all modules
26
- - **Event log race conditions fixed** — sync/async queue unification
27
- - **Subagent record sanitization** — allow-listed field persistence
28
- - **~1,900 new tests**, 113 test files — total suite ~4,600 tests, 0 failures
29
- - **42+ audit rounds, 160+ issues fixed** across all severity levels
30
-
31
- See [SECURITY-ISSUES.md](SECURITY-ISSUES.md) for the full list (SEC-001 SEC-007 all marked fixed).
12
+ **v0.6.4**: See [CHANGELOG.md](CHANGELOG.md).
13
+
14
+ ### Highlights (v0.6.3 → v0.6.4)
15
+
16
+ - **Visually rich tool rendering** — `team` and `Agent` tool calls now render as framed cards in the Pi TUI with box-drawing borders, colored status badges, and structured layouts
17
+ - **Merged call+result into ONE connected frame** — the call header and result body now form a single seamless frame instead of two disconnected boxes
18
+ - **Animated live progress bar during runs** — real-time `████░░░░ N/M` task progress with elapsed time, rendered DURING the run; indeterminate "starting" phase uses an animated scanning bar
19
+ - **Compact completion summary** — collapsed cards show `✓ crew run 3/3 done · 1m2s · 26k tok · $0.068` with expand hint and per-agent briefs
20
+ - **Critical crash fix on session resume** — `renderCall` was returning a `string` instead of a `Text` component, causing `TypeError: child.render is not a function` when Pi re-rendered stored tool calls
21
+ - **Disabled brief tool overrides** — reverted the experimental brief mode that replaced Pi's superior native renderers (syntax highlighting, diff views, full content)
22
+ - **Flaky test fix** — `AnimatedMascot` timing tests made CI-load-robust via polling loops
23
+ - **CI green** — 0 failures on Ubuntu, macOS, and Windows
24
+
25
+ ### Highlights (v0.6.2 v0.6.3)
26
+
27
+ - **137 commits** since v0.6.1 200 files changed (+16,955 / −2,057 lines)
28
+ - **4,792 tests**, 506 test files — **0 failures** across the entire suite
29
+ - **Cross-platform CI green** 0 failures on Ubuntu, macOS, and Windows
30
+ - **366 source files**, ~70K lines of TypeScript
31
+ - **Worktree precondition validation** friendly errors instead of crashes when cwd is not a git repo or repo is dirty
32
+ - **Cross-platform path handling** — `canonicalizePath` with `realpathSync.native` for Windows short-name/long-name aliasing; macOS symlink resolution
33
+ - **Scheduled job lifecycle** — spawned runs are tracked, cancelling a job kills its runs
34
+ - **Heartbeat false-positive fix** — PID liveness gate prevents dead detection during long LLM responses
35
+ - **ENOENT crash fix** — prune/forget race no longer crashes pi when persisting to deleted runs
36
+ - **Pipe buffer deadlock fix** — test runner no longer deadlocks when OS pipe buffer fills
37
+ - **Plugin registry** — extensible framework context injection for Next.js, Vite, Vitest
38
+ - **Health score system** — penalty-based scoring with time-series snapshots
39
+ - **CrewError taxonomy** — E001–E006 structured error codes replacing raw throws
40
+ - **Atomic write v2** — fsync + rename pattern for crash-safe state persistence
41
+ - **Pre-push review**: 56 unpushed commits reviewed, 1 release blocker found and fixed
42
+ - **Security**: sandbox constructor escape strengthened; env-filter provider key handling fixed
43
+ - **State-store race fix** — manifest/tasks mtime false positive eliminated
44
+ - **Orphan worker/temp cleanup** — 4-layer defense with session-scoped tracking
32
45
 
33
46
  ---
34
47
 
@@ -50,6 +63,9 @@ See [SECURITY-ISSUES.md](SECURITY-ISSUES.md) for the full list (SEC-001 – SEC-
50
63
  - **Adaptive plan fanout** — single `assess` step lets a planner pick the smallest effective crew
51
64
  - **Adaptive workflows** — `implementation`, `review`, `parallel-research`, `research` workflows ship in `workflows/`
52
65
  - **Hardened secrets** — linear-time detection covers PEM keys, Authorization headers, Bearer tokens, and `key=value` patterns
66
+ - **Scheduled runs** — `schedule`/`scheduled` actions with cron, interval, and one-shot support; spawned runs tracked and auto-cancelled on job removal
67
+ - **Plugin system** — framework-aware context injection (Next.js, Vite, Vitest) via plugin registry
68
+ - **Health scoring** — penalty-based run health with time-series snapshots
53
69
 
54
70
  ---
55
71
 
@@ -120,13 +136,13 @@ When unsure which team/workflow fits:
120
136
 
121
137
  ## Builtin Teams
122
138
 
123
- | Team | Workflow | Mục đích |
139
+ | Team | Workflow | Purpose |
124
140
  |------|----------|----------|
125
- | `default` | explore → plan → execute → verify | Cân bằng, đa năng |
126
- | `fast-fix` | explore → execute → verify | Sửa bug nhỏ nhanh |
127
- | `implementation` | Adaptive planner quyết fanout | Multi-file implementation |
141
+ | `default` | explore → plan → execute → verify | Balanced, general-purpose |
142
+ | `fast-fix` | explore → execute → verify | Quick bug fixes |
143
+ | `implementation` | Adaptive planner decides fanout | Multi-file implementation |
128
144
  | `review` | explore → code-review → security-review → verify | Code review + security audit |
129
- | `research` | explore → analyze → write | Nghiên cứu viết tài liệu |
145
+ | `research` | explore → analyze → write | Research and documentation |
130
146
  | `parallel-research` | Parallel shards → synthesize → write | Multi-source research |
131
147
 
132
148
  ## Builtin Agents
@@ -180,7 +196,7 @@ Worktree mode creates an **isolated git worktree per task** — safe for paralle
180
196
  "action": "run",
181
197
  "team": "implementation",
182
198
  "goal": "Refactor auth",
183
- "worktree": { "enabled": true }
199
+ "workspaceMode": "worktree"
184
200
  }
185
201
  ```
186
202
 
@@ -189,10 +205,13 @@ Worktree mode creates an **isolated git worktree per task** — safe for paralle
189
205
  ```
190
206
 
191
207
  Requirements:
192
- - Git repository
193
- - Clean working tree (no uncommitted changes in the main worktree)
208
+ - Git repository (cwd must be inside a git repo)
209
+ - Clean working tree (no uncommitted changes in the leader worktree)
210
+ - Can be disabled via config: `requireCleanWorktreeLeader: false`
194
211
  - Worktrees auto-cleanup on run completion/cancel
195
212
 
213
+ If preconditions are not met, a friendly error message is returned instead of crashing.
214
+
196
215
  ---
197
216
 
198
217
  ## Configuration
@@ -255,11 +274,18 @@ Requirements:
255
274
  { "action": "artifacts", "runId": "team_..." }
256
275
  { "action": "cancel", "runId": "team_..." }
257
276
  { "action": "resume", "runId": "team_..." }
277
+ { "action": "retry", "runId": "team_..." }
278
+ { "action": "steer", "runId": "team_...", "taskId": "01_explore", "message": "Focus on src/ only" }
279
+ { "action": "respond", "runId": "team_...", "message": "Answer" }
280
+ { "action": "wait", "runId": "team_..." }
258
281
 
259
282
  // Discovery
260
283
  { "action": "list" }
261
- { "action": "get", "resource": "team", "name": "default" }
284
+ { "action": "get", "resource": "team", "team": "default" }
285
+ { "action": "get", "resource": "agent", "agent": "explorer" }
286
+ { "action": "get", "resource": "workflow", "workflow": "review" }
262
287
  { "action": "recommend", "goal": "Refactor auth flow" }
288
+ { "action": "search", "goal": "heartbeat detection" }
263
289
 
264
290
  // Resource management
265
291
  { "action": "create", "resource": "agent", "config": { "name": "api-reviewer", ... } }
@@ -281,12 +307,34 @@ Requirements:
281
307
  { "action": "autonomy", "profile": "assisted" }
282
308
 
283
309
  // Advanced
284
- { "action": "api", "runId": "team_...", "operation": "read-manifest" }
310
+ { "action": "api", "runId": "team_...", "config": { "operation": "read-manifest" } }
285
311
  { "action": "plan", "team": "default", "goal": "..." }
312
+ { "action": "orchestrate", "planPath": "plan.md", "team": "implementation", "goal": "..." }
313
+ { "action": "parallel", "config": { "tasks": [{"goal": "...", "agent": "explorer"}] } }
286
314
  { "action": "worktrees", "runId": "team_..." }
315
+ { "action": "graph", "runId": "team_..." }
316
+ { "action": "explain", "runId": "team_..." }
317
+ { "action": "health" }
318
+ { "action": "doctor" }
319
+ { "action": "cache" }
320
+ { "action": "invalidate", "runId": "team_..." }
321
+
322
+ // Scheduled runs
323
+ { "action": "schedule", "team": "fast-fix", "goal": "Run tests", "cron": "0 9 * * MON" }
324
+ { "action": "schedule", "team": "default", "goal": "...", "interval": 3600000 }
325
+ { "action": "schedule", "team": "research", "goal": "...", "once": "+10m" }
326
+ { "action": "scheduled" }
327
+
328
+ // Diagnostics & settings
329
+ { "action": "config" }
330
+ { "action": "settings" }
331
+ { "action": "autonomy" }
332
+ { "action": "anchor" }
333
+ { "action": "onboard" }
334
+ { "action": "auto-summarize" }
287
335
  ```
288
336
 
289
- 📖 Full actions reference (28 actions): [docs/actions-reference.md](docs/actions-reference.md)
337
+ 📖 Full actions reference (40+ actions): [docs/actions-reference.md](docs/actions-reference.md)
290
338
 
291
339
  ---
292
340
 
@@ -381,13 +429,13 @@ Your system prompt here.
381
429
  ```bash
382
430
  cd pi-crew
383
431
  npm install # dependencies
384
- npm test # unit + integration tests
432
+ npm test # unit + integration tests (~4,800 tests)
385
433
  npm run typecheck # tsc --noEmit
386
434
  npm run ci # full CI-equivalent check
387
435
  npm pack --dry-run # package verification
388
436
  ```
389
437
 
390
- CI runs on: `ubuntu-latest` · `windows-latest` · `macos-latest`
438
+ Stats: **366 source files** (70K lines) · **506 test files** (66K lines) · **4,792 tests, 0 failures** · **CI: Ubuntu ✅ macOS ✅ Windows ✅**
391
439
 
392
440
  ---
393
441
 
@@ -0,0 +1,189 @@
1
+ # Issue #29 Analysis: Hardcoded `.crew/state/runs` Path Crashes pi
2
+
3
+ > **Status**: ANALYZED — no code changes applied (per user request).
4
+ > **Reporter**: sethmorton (external, opened 2026-06-05)
5
+ > **Affects**: pi-crew 0.6.0 / 0.6.1 (issue present in v0.6.1 too — not fixed in any post-v0.6.1 commit as of `e95e055`).
6
+ > **Severity**: **CRITICAL** — full-harness uncaughtException, can crash pi.
7
+
8
+ ---
9
+
10
+ ## 1. Reporter's Claims — Verification Matrix
11
+
12
+ | Claim | Verified? | Evidence |
13
+ |---|---|---|
14
+ | `waitForRun()` hardcodes `.crew/state/runs/<runId>` at `run-tracker.ts:82` | ✅ YES | `src/runtime/run-tracker.ts:82` — `const runDir = path.join(cwd, ".crew", "state", "runs", runId);` |
15
+ | `projectCrewRoot()` correctly resolves to `.pi/teams/` for `.pi/`-only projects | ✅ YES | `src/utils/paths.ts:88-97` |
16
+ | The throw escapes as an unhandled promise rejection | ✅ PARTIAL | Both direct call sites (`team-tool.ts:817`, `team-tool/run.ts:211,344`) wrap in `try/catch`. **However**, the issue is real when `waitForRun` is called indirectly through `subagent-manager.ts`. |
17
+ | `subagent-manager.ts:250` re-throws via `record.promise` IIFE | ⚠️ LINE-DRIFT | Issue says `~line 250` — in current code (commit `e95e055`), `record.promise` IIFE starts at line 258 and the `throw error;` is at line 281. The mechanism is correct; line numbers drifted from prior code. |
18
+
19
+ ## 2. Root Cause Analysis
20
+
21
+ ### Why it crashes (in `.pi/`-only projects)
22
+
23
+ `projectCrewRoot(cwd)` (in `src/utils/paths.ts:88-97`) returns:
24
+
25
+ ```
26
+ 1. .crew/ (if exists)
27
+ 2. .pi/teams/ (if .pi/ exists and .crew/ doesn't)
28
+ 3. .crew/ (default, would be created by ensureCrewDirectory)
29
+ ```
30
+
31
+ In a `.pi/`-based project **without** `.crew/`, run state lives at:
32
+ ```
33
+ .pi/teams/state/runs/<runId>/
34
+ ```
35
+
36
+ But `waitForRun()` (line 82) hardcodes:
37
+ ```
38
+ .crew/state/runs/<runId>/
39
+ ```
40
+
41
+ The early-exit check `fs.existsSync(runDir)` returns `false`, throwing:
42
+ ```
43
+ Error: Run <runId> not found. No run directory at <cwd>/.crew/state/runs/<runId>
44
+ ```
45
+
46
+ ### Why this becomes a `uncaughtException`
47
+
48
+ The throw in `waitForRun` propagates differently depending on caller:
49
+
50
+ | Caller | Wrapped in try/catch? | Result |
51
+ |---|---|---|
52
+ | `team-tool.ts:817` (wait action) | ✅ YES | Returns error to user — safe |
53
+ | `team-tool/run.ts:211` (async.run) | ✅ YES | Returns error to user — safe |
54
+ | `team-tool/run.ts:344` (foreground) | ✅ YES | Returns error to user — safe |
55
+ | **`subagent-manager.ts:270` (pollRunToTerminal)** | ⚠️ **NO** | `record.promise` rejects → line 281 `throw error` re-throws → if no caller awaits → **unhandled rejection** |
56
+
57
+ The `subagent-manager.ts` path is the actual crash site. Direct `waitForRun` calls in `team-tool.ts` are safe.
58
+
59
+ ### Why the run "exists" in `.pi/teams/...`
60
+
61
+ The background worker process (`background-runner.ts`) correctly **reads/writes** to the right path **once it uses `projectCrewRoot()`** — but it does NOT. The same hardcoding is present in `background-runner.ts:139, 172`. So the **background worker itself is writing to `.crew/state/runs/...` (wrong path)**, while the **manifest claims it lives at `.pi/teams/state/runs/...`** (correct path).
62
+
63
+ Wait — let me re-verify this. The manifest is created by which process?
64
+
65
+ Actually, the **run manifest is created at the orchestrator** (using `projectCrewRoot`), and the **background worker reads/writes log/exit-code files using the same wrong path**. The manifest's `stateRoot` field is correct (`.pi/teams/...`), but the background worker writes its log to `<cwd>/.crew/state/runs/.../background.log`, which is the wrong path.
66
+
67
+ Result: the manifest correctly points to `.pi/teams/...` but the background worker logs/exit-codes go to `.crew/.../background.log`. The manifest loader (`loadRunManifestById` → `scopeBaseRoot` → `projectCrewRoot`) reads from the correct path. So if the manifest is created and persists, polling SHOULD work. The early-exit `fs.existsSync` check fires only on the very first poll iteration before any manifest has been written.
68
+
69
+ ## 3. Affected Sites — Complete Inventory
70
+
71
+ Hardcoded `.crew/state/runs/...` paths that ignore `projectCrewRoot()`:
72
+
73
+ | File | Line | Code | Used for |
74
+ |---|---|---|---|
75
+ | `src/runtime/run-tracker.ts` | 82 | `path.join(cwd, ".crew", "state", "runs", runId)` | **CRASH SITE**: `waitForRun()` early-exit |
76
+ | `src/runtime/background-runner.ts` | 139 | `path.join(_cwd, ".crew/state/runs", _runId, "background.log")` | Background worker log redirect |
77
+ | `src/runtime/background-runner.ts` | 172 | `path.join(cwd, ".crew", "state", "runs", runId, "exit-code.txt")` | Exit code persistence |
78
+ | `src/runtime/skill-effectiveness.ts` | 115 | `join(process.cwd(), ".crew/state/runs/${runId}/skill-metrics.jsonl")` | Skill metrics storage |
79
+ | `src/runtime/skill-effectiveness.ts` | 125 | `join(process.cwd(), ".crew/state/runs/${runId}/skill-activations.jsonl")` | Skill activation storage |
80
+ | `src/runtime/checkpoint.ts` | 166 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `saveCheckpoint` |
81
+ | `src/runtime/checkpoint.ts` | 177 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `loadCheckpoint` |
82
+ | `src/runtime/checkpoint.ts` | 188 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `clearCheckpoint` |
83
+ | `src/runtime/checkpoint.ts` | 199 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `hasCheckpoint` |
84
+ | `src/runtime/checkpoint.ts` | 209 | `path.join(process.cwd(), ".crew/state/runs", runId)` | `listCheckpoints` |
85
+ | `src/state/decision-ledger.ts` | 29 | `stateRoot ?? \`.crew/state/runs/${runId}\`` | `getLedgerPath` fallback (always hit when called without `stateRoot`) |
86
+
87
+ **11 sites across 5 files** confirmed affected.
88
+
89
+ ### Already correct (for reference)
90
+
91
+ | File | Line | Why correct |
92
+ |---|---|---|
93
+ | `src/state/crew-init.ts` | 209 | `safeJoin(crewRoot, "state", "runs")` — uses `crewRoot` parameter |
94
+ | `src/extension/team-tool.ts` | 1210 | `path.join(projectCrewRoot(ctx.cwd), "state", "runs", params.runId)` ✓ |
95
+ | `src/extension/team-onboard.ts` | 28 | `path.join(crewRoot, "state", "runs")` — uses parameter |
96
+ | `src/runtime/stale-reconciler.ts` | 389 | Inside `tmpDir` workspace scan — these are pi-crew's own zombie workspaces, not user `.pi/` projects, so this is acceptable (but inconsistent) |
97
+
98
+ ## 4. Why the Bug Survives (it should have been caught)
99
+
100
+ - ✅ `loadRunManifestById` and `loadRunManifestByIdAsync` in `state-store.ts` correctly use `projectCrewRoot()` via `scopeBaseRoot` → `useProjectState`.
101
+ - ❌ `waitForRun()` and the 10 other sites above bypass the resolver and hardcode the path.
102
+ - The hardcoded path is **inconsistent** with the resolver — clearly a copy-paste/regression.
103
+ - The issue is **only triggered in `.pi/`-only projects** (no `.crew/`). Standard `.crew/` projects (the original pi-crew use case) never hit it.
104
+
105
+ ## 5. Recommended Fix (NOT APPLIED per user request)
106
+
107
+ ### Phase 1 — Use the resolver everywhere
108
+
109
+ Replace each hardcoded path with:
110
+
111
+ ```ts
112
+ import { projectCrewRoot } from "../utils/paths.ts";
113
+ // ...
114
+ const stateRoot = path.join(projectCrewRoot(cwd), "state", "runs", runId);
115
+ ```
116
+
117
+ For sites that take `process.cwd()`, switch to taking a `cwd` parameter (callers pass the real cwd). This is a small refactor for `skill-effectiveness.ts` and `checkpoint.ts` since their current API uses `process.cwd()`.
118
+
119
+ ### Phase 2 — Defense in depth (the reporter's second suggestion)
120
+
121
+ The reporter's point about `subagent-manager.ts` is also valid: **a single run failure should never crash the harness**. The current pattern at line 281 `throw error; // H4: Propagate rejection` deliberately rejects the promise, but if no one awaits it, it becomes unhandled.
122
+
123
+ Two options:
124
+
125
+ **A) Add a safety net in `subagent-manager.ts` (recommended)**:
126
+
127
+ ```ts
128
+ private start(record: SubagentRecord, ...) {
129
+ // ...
130
+ record.promise = (async () => { ... })();
131
+ // Defense in depth: a subagent failure should never crash pi.
132
+ // Attach a no-op catch so that even if no caller awaits, the rejection
133
+ // doesn't propagate to unhandledRejection → uncaughtException.
134
+ record.promise.catch((error) => {
135
+ logInternalError("subagent-manager.start.unhandled", error, `id=${record.id}`);
136
+ });
137
+ }
138
+ ```
139
+
140
+ This protects the harness against **any** future rejection path, not just the one fixed in Phase 1.
141
+
142
+ **B) Remove the `throw error;` at line 281** — the record is already marked `error` status, the caller can read it. This is a deeper change to the API contract.
143
+
144
+ Option (A) is the smaller, safer change.
145
+
146
+ ## 6. Test Coverage
147
+
148
+ Tests exist for `checkpoint` (`checkpoint.test.ts`, `checkpoint-cov.test.ts`) and `skill-effectiveness` (`skill-effectiveness.test.ts`) but they likely use a `.crew/`-based temp project, so they wouldn't catch this bug. A regression test should:
149
+
150
+ 1. Create a temp project with `.pi/` (no `.crew/`)
151
+ 2. Call each affected function (`waitForRun`, `saveCheckpoint`, `getSkillMetricsPath`, etc.)
152
+ 3. Assert the result path is `.pi/teams/...` (not `.crew/...`)
153
+
154
+ For the `subagent-manager.ts` defense-in-depth: spawn a subagent whose `runner` rejects, assert pi does not exit and the record is marked `error`.
155
+
156
+ ## 7. Impact Assessment
157
+
158
+ - **Blast radius**: All pi-crew users with `.pi/`-only projects.
159
+ - **Trigger frequency**: Low — requires the background worker to fail/die AND `waitForRun` to be called indirectly via `subagent-manager.ts`. The `team-tool` direct paths are safe.
160
+ - **Severity**: CRITICAL — full-harness crash with misleading "Run not found" error pointing at a path that, by design, should never exist.
161
+
162
+ ## 8. Recommended PR Description (draft)
163
+
164
+ > **Title**: fix(issue #29): use projectCrewRoot() for run-state paths in all 11 affected sites
165
+ >
166
+ > **Body**:
167
+ >
168
+ > The `.pi/`-based project layout (`.pi/teams/`) is not handled by the 11 hardcoded `.crew/state/runs/...` sites listed in the analysis. The most severe site is `waitForRun()` in `src/runtime/run-tracker.ts:82`, which throws on a non-existent path and (via `subagent-manager.ts:281`) can escape as an unhandled rejection that crashes pi.
169
+ >
170
+ > Fix:
171
+ > 1. Replace all 11 hardcoded paths with `path.join(projectCrewRoot(cwd), "state", "runs", runId)`.
172
+ > 2. For sites that use `process.cwd()` (`skill-effectiveness.ts`, `checkpoint.ts`), switch to taking a `cwd` parameter.
173
+ > 3. Add `record.promise.catch(...)` in `subagent-manager.ts:start()` as defense in depth — a subagent failure should never crash the harness.
174
+ > 4. Add regression tests using a `.pi/`-only temp project.
175
+ >
176
+ > Closes #29.
177
+
178
+ ---
179
+
180
+ ## Status
181
+
182
+ - ✅ Issue reproduced in code (all 11 sites verified)
183
+ - ✅ Root cause identified
184
+ - ✅ Fix plan drafted
185
+ - ❌ **Code changes NOT applied** (per user request: "chưa thực hiện sửa gì cả")
186
+ - ❌ Tests NOT added
187
+ - ❌ v0.6.2 NOT released
188
+
189
+ Sẵn sàng apply khi user yêu cầu.