#20859: fix(hooks): wire message_sent hook into reply dispatcher for all channels
agents
size: M
trusted-contributor
## Summary
Centralises `message_sent` hook firing in the reply dispatcher rather than scattering it across per-channel delivery paths. Hooks fire inline after each successful payload delivery with channel/account/conversation context.
Currently `message_sent` only fires from `deliverOutboundPayloadsCore` for the subset of channels that flow through it. This PR adds a second firing point in `createReplyDispatcher` so that **all** outbound payloads (including block/tool replies in DM sessions) trigger the hook with full routing context.
## Changes
- **`reply-dispatcher.ts`** — Add `hookContext` option to `createReplyDispatcher`; fire `message_sent` hook after successful delivery with `hasHooks("message_sent")` guard; errors are swallowed (fire-and-forget)
- **`deliver.ts`** — Add `conversationId: to` to the `message_sent` hook context in `deliverOutboundPayloadsCore`
- **`system-prompt.ts`** — Guard context file heading against undefined `file.path`
## Test plan
- [x] 5 new tests for `message_sent` hook in `reply-dispatcher.test.ts`:
- Fires after successful delivery with correct payload/context
- Passes hookContext through to hook context
- Does NOT fire when delivery fails
- Does NOT fire when normalized text is empty
- Hook errors don't break delivery (fire-and-forget)
- [x] 2 new tests for `message_sent` hook in `deliver.test.ts`:
- Fires per-payload after successful delivery with conversationId
- Hook errors don't propagate to caller
- [x] All existing tests pass (34 total across affected files)
- [x] `oxlint` — 0 errors, 0 warnings
---
_Re-opened from #15825 (auto-closed during fork maintenance)._
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Centralizes `message_sent` hook firing to ensure all outbound messages trigger the hook. Previously only channels using `deliverOutboundPayloadsCore` fired the hook; this PR adds a second firing point in `createReplyDispatcher` to cover all channels (including DM sessions).
Key changes:
- Added optional `hookContext` parameter to `createReplyDispatcher` for channel/account/conversation routing context
- Hook fires after successful delivery with guards for `hasHooks("message_sent")`, non-empty text, and valid `channelId`
- Errors are properly swallowed (fire-and-forget pattern) to prevent hook failures from breaking delivery
- Added `conversationId: to` to hook context in `deliver.ts` for consistency
- Fixed undefined path guard in `system-prompt.ts` (defensive coding improvement)
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with comprehensive test coverage and defensive implementation
- The changes are well-tested (7 new tests covering success, failure, and error cases), use defensive coding patterns (optional chaining, fire-and-forget error handling), and the opt-in `hookContext` design prevents accidental double-firing. The implementation properly guards against edge cases (empty text, missing context) and errors are swallowed to ensure hook failures don't break message delivery.
- No files require special attention
<sub>Last reviewed commit: bf4440b</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#19922: feat(hooks): add message:received and message:sent hook events
by NOVA-Openclaw · 2026-02-18
84.7%
#9906: feat: wire message_sending hook in outbound delivery
by teempai · 2026-02-05
81.9%
#8084: fix(plugins): wire up message_sending hook in outbound delivery
by lailoo · 2026-02-03
81.5%
#12584: feat(hooks): wire outbound message lifecycle hooks
by vincentkoc · 2026-02-09
80.2%
#7545: feat(hooks): add message:received hook for pre-turn automation
by wangtian24 · 2026-02-02
78.8%
#10109: feat(plugins): invoke message_received and message_sent hooks
by nezovskii · 2026-02-06
78.5%
#11597: feat(hooks): implement message:received hook
by gnufoo · 2026-02-08
78.2%
#18004: feat: Add before_message_dispatch hook for blocking inbound messages
by Andy-Haigh · 2026-02-16
76.8%
#12986: feat(hooks): wire message_sending and message_sent plugin hooks
by Ramsbaby · 2026-02-10
76.5%
#7580: feat: add message:received internal hook with prompt injection
by rodrigoschott · 2026-02-03
76.3%