← Back to PRs

#20859: fix(hooks): wire message_sent hook into reply dispatcher for all channels

by davidrudduck open 2026-02-19 10:55 View on GitHub →
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