← Back to PRs

#18801: fix(routing): use fresh config in resolveAgentRoute to prevent stale bindings

by mcaxtr open 2026-02-17 02:49 View on GitHub →
channel: discord channel: telegram channel: whatsapp-web size: S experienced-contributor
## Summary - `resolveAgentRoute()` now calls `loadConfig()` internally instead of relying on `input.cfg`, which was often a stale closure snapshot captured at channel handler startup - Falls back to `input.cfg` when `loadConfig()` degrades to `{}` (transient parse/read failures) or throws (e.g. `DuplicateAgentDirError`) - Makes `cfg` optional on `ResolveAgentRouteInput` (deprecated — config is now loaded internally) - Adds tests verifying both fallback paths Fixes #18773 ## Root Cause Channel handlers across Telegram, LINE, Slack, Signal, Discord, Mattermost, iMessage, LINQ, and WhatsApp auto-reply all capture `cfg` once at startup and pass it through closures to `resolveAgentRoute()`. When config is hot-reloaded (e.g., bindings updated via the configure wizard or gateway API), the closure config is stale and routing decisions use outdated binding data. Four call sites had already been individually patched to use `loadConfig()`: - `src/telegram/bot-message-context.ts:172` - `src/telegram/bot-handlers.ts:533` - `src/discord/monitor/message-handler.preflight.ts:244` - `src/web/auto-reply/monitor/on-message.ts:68` Including one with an explicit comment: *"Fresh config for bindings lookup"* (commit `8d96955e1`). But 15+ call sites still passed stale config. This fix moves the `loadConfig()` call into `resolveAgentRoute()` itself, fixing all callers at once. Since `loadConfig()` is sync with a TTL cache, there is no performance penalty. Related issues: #5283, #13869 Supersedes: #14915 ## Test plan - [x] All 38 routing tests pass (35 existing + 3 new fallback tests) - [x] Verified fallback: when `loadConfig()` returns `{}`, routing uses caller-provided `input.cfg` - [x] Verified fallback: when `loadConfig()` throws, routing uses caller-provided `input.cfg` - [x] Verified preference: when `loadConfig()` returns valid config, it takes precedence over stale `input.cfg` - [x] Build passes (`pnpm build`) - [x] Format and lint clean (`oxfmt --check`, `oxlint`) <!-- greptile_comment --> <h3>Greptile Summary</h3> Moves `loadConfig()` call inside `resolveAgentRoute()` to ensure hot-reloaded binding changes take effect immediately across all channel handlers (Telegram, Discord, Slack, Signal, WhatsApp, and others). Previously, channel handlers captured config at startup in closures, causing stale routing decisions when config was updated via the configure wizard or gateway API. **Key changes:** - `resolveAgentRoute()` now calls `loadConfig()` internally on line 305 and prefers fresh config over caller-provided `input.cfg` - Falls back to `input.cfg` when `loadConfig()` returns empty object (transient parse/read failures) or throws (e.g., `DuplicateAgentDirError`) - Makes `cfg` optional on `ResolveAgentRouteInput` (line 28) with deprecation notice since config is now loaded internally - Removes `loadConfig()` imports from 4 call sites that had already been individually patched (Discord, Telegram 2x, WhatsApp) - All other call sites continue passing `cfg` as before, which now acts as fallback **Test coverage:** - 3 new tests verify fallback behavior: empty config fallback, fresh config preference, and exception fallback - All 38 routing tests pass (35 existing + 3 new) <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk - The fix is well-designed with proper fallback handling, comprehensive test coverage (38 tests), and backwards compatibility. The logic correctly handles edge cases (empty config, exceptions), and the approach fixes the root cause for all channels at once rather than patching individual call sites. - No files require special attention <sub>Last reviewed commit: 5c433b0</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs