← Back to PRs

#20858: fix(hooks): normalize hook addresses to canonical provider:kind:id format

by davidrudduck open 2026-02-19 10:55 View on GitHub →
agents size: L trusted-contributor
## Summary Extends the existing `${provider}:${kind}:${finalId}` pattern from `resolveGroupSessionKey` (src/config/sessions/group.ts) to hook event payloads (`message_received`, `message_sent`). Currently, hook consumers receive raw, inconsistent address formats — bare snowflakes, `channel:` prefixed IDs, `user:` prefixed IDs, or partially-qualified `telegram:999` — depending on the provider and chat type. This makes it impossible for plugins to reliably key on channel identity without re-implementing parsing logic. This PR normalizes all hook address fields to the canonical `provider:kind:id` format (e.g. `discord:channel:123`, `telegram:user:999`, `whatsapp:group:123@g.us`), consistent with the vocabulary already used in upstream's group session key resolution. ## Changes - **`toCanonicalAddress()`** — new function that normalizes any raw address + context into `provider:kind:id` format - **`parseRoutingAddress()`** — new parser that extracts `{ provider?, kind?, id }` from any address format - **`chatTypeToKind()`** — maps chatType values (`direct`/`group`/`channel`/`supergroup`) to canonical kind (`user`/`group`/`channel`) - Applied to `from`, `to`, `originatingTo`, and `conversationId` fields in the `message_received` hook payload - Guards against null/undefined/empty/object-typed inputs - Idempotent — already-canonical addresses pass through unchanged - Naming aligned with upstream's `provider`/`kind`/`id` vocabulary ## Test plan - [x] 41 tests covering `parseRoutingAddress`, `toCanonicalAddress`, and integration with `dispatchReplyFromConfig` - [x] Discord guild → `discord:channel:<snowflake>` - [x] Discord DM → `discord:user:<snowflake>` - [x] Telegram DM/group → `telegram:user:<id>` / `telegram:group:<id>` - [x] WhatsApp DM/group → `whatsapp:user:<e164>` / `whatsapp:group:<jid>` - [x] Signal DM/group → `signal:user:<e164>` / `signal:group:<id>` - [x] Slack channel → `slack:channel:<id>` - [x] iMessage DM → `imessage:user:<id>` - [x] Bare snowflakes → `unknown:user:<id>` - [x] Null/undefined/empty/whitespace → `undefined` - [x] Idempotency — already-canonical addresses preserved --- _Re-opened from #15824 (auto-closed during fork maintenance)._ <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR introduces address normalization for hook event payloads, standardizing all routing addresses to the canonical `provider:kind:id` format (e.g. `discord:channel:123`, `telegram:user:999`). This aligns with the existing pattern in `resolveGroupSessionKey` and ensures hook consumers receive consistent address formats across all messaging platforms. **Key changes:** - Added `parseRoutingAddress()` to parse addresses in various formats (`provider:kind:id`, `provider:id`, `kind:id`, or bare `id`) - Added `toCanonicalAddress()` to normalize any address format to canonical form using context (provider, chatType) - Added `chatTypeToKind()` helper to map chatType values to canonical kinds - Applied normalization to `from`, `to`, `originatingTo`, and `conversationId` fields in `message_received` hook payloads - Added `guildId` and `channelName` metadata fields to hook payloads - Comprehensive test coverage (41 tests) for parsing, normalization, and integration <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk - The implementation is well-designed with comprehensive test coverage (41 tests), handles edge cases properly (null/undefined/empty/whitespace), and maintains backward compatibility through idempotency. The normalization logic is defensive and falls back gracefully when context is missing. - No files require special attention <sub>Last reviewed commit: 870dbf7</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs