#16915: fix: await compaction hooks with timeout to prevent cross-session data bleed
agents
size: S
Cluster:
Compaction Hooks Enhancement
## Problem
Compaction hooks (before_compaction / after_compaction) were fire-and-forget:
- `process.chdir` was restored before hooks finished, so hooks ran in the wrong working directory
- Message arrays were passed by reference, allowing hooks to mutate session state
- No timeout meant a stuck hook could hang lane cleanup indefinitely
This is especially dangerous in multi-agent setups where multiple sessions compact concurrently.
Fixes #15990
## Solution
1. **Deep-clone messages** passed to hooks using `structuredClone` (with manual fallback for non-cloneable objects)
2. **Await hook promises** via `Promise.allSettled()` in `finally` before restoring `process.chdir`/env
3. **Add 10s timeout** (`waitForHookWithTimeout`) so stuck hooks log a warning and release the lane
4. Apply the same pattern to both code paths:
- `compactEmbeddedPiSessionDirect` (direct compaction)
- `handleAutoCompactionStart` / `handleAutoCompactionEnd` (auto-compaction lifecycle)
## Tests
- New `compact.test.ts`: verifies hooks are awaited before `process.chdir` restore, and messages are deep-cloned (mutations don't leak back)
- Extended `wired-hooks-compaction.test.ts`: verifies await behavior and clone isolation in auto-compaction handlers
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a real concurrency issue where compaction hooks were fire-and-forget, causing `process.chdir` to be restored before hooks finished (so hooks ran in the wrong working directory) and allowing hooks to mutate session state via shared message references.
The fix is well-structured:
- Deep-clone messages via `structuredClone` (with manual fallback) before passing to hooks
- Await hook promises in `finally` blocks via `Promise.allSettled` before restoring `process.chdir`/env
- Add a 10s timeout (`waitForHookWithTimeout`) to prevent stuck hooks from hanging lane cleanup
- Apply the same pattern to both direct compaction and auto-compaction lifecycle handlers
Key observations:
- The `void` prefix on the now-async auto-compaction handlers in `pi-embedded-subscribe.handlers.ts` is correct — the event handler is synchronous by design, and internal error handling (`.catch`) prevents unhandled promise rejections
- The type change to `PluginHookAfterCompactionEvent` (adding `messages?: unknown[]`) correctly aligns the type with runtime behavior
- ~63 lines of helper code (`waitForHookWithTimeout`, `cloneMessageForHook`, `cloneMessagesForHook`) are duplicated across `compact.ts` and `pi-embedded-subscribe.handlers.compaction.ts` — extracting to a shared module would reduce maintenance burden
- Tests are thorough: they verify hooks are awaited before cwd restore, messages are deep-cloned (mutations don't leak), and stuck hooks time out correctly
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge — it fixes a real concurrency bug with correct timeout/cleanup semantics and thorough tests.
- The core logic changes are sound: hooks are properly awaited before cwd/env restoration, messages are deep-cloned to prevent mutation leaks, and timeouts prevent indefinite hangs. The only concern is code duplication of ~63 lines of helper functions across two files, which is a maintainability issue rather than a correctness issue. Tests cover the key scenarios well.
- No files have correctness issues. `src/agents/pi-embedded-subscribe.handlers.compaction.ts` and `src/agents/pi-embedded-runner/compact.ts` share duplicated helper code that could be extracted to a shared module.
<sub>Last reviewed commit: de0c4eb</sub>
<!-- greptile_other_comments_section -->
<sub>(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#3749: fix(plugins): invoke before_compaction and after_compaction hooks d...
by taronsung · 2026-01-29
86.1%
#13861: feat(hooks): add session:compaction hook event
by lailoo · 2026-02-11
84.0%
#16788: feat(hooks): emit compaction lifecycle hooks
by vincentkoc · 2026-02-15
83.3%
#8244: feat(hooks): add session:before_compact and session:after_compact i...
by kephail · 2026-02-03
80.0%
#23019: fix(hooks): use globalThis singleton for internal hooks handlers Map
by karmafeast · 2026-02-21
78.7%
#6268: fix: add timeout to compaction retry to prevent session lockout
by batumilove · 2026-02-01
78.6%
#15571: feat: infrastructure foundation — hooks, model failover, sessions, ...
by tangcruz · 2026-02-13
78.6%
#19177: fix: use parseAgentSessionKey instead of fragile split pattern
by El-Patronum · 2026-02-17
78.5%
#3392: fix(hooks): remove debug console.log statements from session-memory...
by WinJayX · 2026-01-28
78.3%
#17445: fix(pi-embedded): add aggregate timeout to compaction retry + harde...
by joeykrug · 2026-02-15
78.3%