← Back to PRs

#13489: fix: preserve Slack channel/user ID case in target normalization

by sandieman2 open 2026-02-10 15:41 View on GitHub →
channel: slack stale
## Summary Fixes #4751 and #10668. Slack channel and user IDs are case-sensitive (e.g. `C0ADKTLND7U`, `U025D9U8U`). The `normalizeTargetId()` function was lowercasing the entire target string (`channel:C0ADKTLND7U` → `channel:c0adktlnd7u`), causing Slack API calls (reactions, sends, reads) to fail with `Unknown channel`. ## Changes ### 1. `src/channels/targets.ts` — `normalizeTargetId()` Only lowercase the kind prefix, preserve the ID: ```diff - return \`\${kind}:\${id}\`.toLowerCase(); + return \`\${kind.toLowerCase()}:\${id}\`; ``` ### 2. `src/infra/outbound/target-normalization.ts` — `normalizeTargetForProvider()` Remove `.toLowerCase()` from the fallback path: ```diff - plugin?.messaging?.normalizeTarget?.(raw) ?? (raw.trim().toLowerCase() || undefined); + plugin?.messaging?.normalizeTarget?.(raw) ?? (raw.trim() || undefined); ``` ### 3. `src/infra/outbound/message-action-runner.ts` — Slack ID auto-correction When an agent passes a Slack channel ID (like `C0ADKTLND7U`) as the `channel` parameter (which expects a provider name like `slack`), auto-detect and reinterpret: - Adds `isSlackChannelHint()` to detect Slack-style IDs - When in Slack context, rewrites `channel` to `"slack"` and moves the ID to `target` ## Tests - Updated existing Slack target tests to expect preserved case - Added new `target-normalization.test.ts` with Slack case-preservation tests - Added regression test for Slack IDs passed as `channel` parameter - All 38 related tests pass ✅ ## Notes - Discord IDs are numeric and unaffected by case — no regression there - The `preserveTargetCase()` function in `target-resolver.ts` was a partial workaround for Slack but only covered certain paths; this fix addresses the root cause <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR adjusts target normalization to preserve case for Slack channel/user IDs, fixing failures caused by lowercasing IDs during normalization. Specifically: - `normalizeTargetId()` now lowercases only the kind prefix (`channel:`/`user:`) while keeping the ID unchanged. - `normalizeTargetForProvider()` no longer lowercases inputs on the fallback path when no plugin normalizer is available. - `runMessageAction()` adds a Slack-context heuristic to treat Slack-looking IDs accidentally passed via the `channel` param as the action target, and forces `channel` to `slack`. The changes align normalization behavior with Slack’s case-sensitive identifiers and extend test coverage for Slack case preservation and the new heuristic rewrite behavior. <h3>Confidence Score: 4/5</h3> - Mostly safe to merge, but the Slack-channel heuristic rewrite should be tightened to avoid overriding explicit channel selection in ambiguous cases. - Core normalization changes are small and well-covered by tests. The main risk is the new `channel`→`slack` rewrite based on a regex + Slack context, which mutates params before resolution/policy enforcement and could redirect actions unexpectedly when `channel` contains a Slack-looking string. - src/infra/outbound/message-action-runner.ts <!-- greptile_other_comments_section --> <sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub> <!-- /greptile_comment -->

Most Similar PRs