fix(slack): land #26878 allowlist channel ID case-insensitive match (thanks @lbo728)

Land contributor PR #26878 from @lbo728; include changelog credit and regression tests.

Co-authored-by: lbo728 <extreme0728@gmail.com>
This commit is contained in:
Peter Steinberger
2026-02-26 02:20:40 +00:00
parent 958cafc54f
commit 069bbf9741
3 changed files with 30 additions and 0 deletions

View File

@@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai
- Discord/Typing indicator: prevent stuck typing indicators by sealing channel typing keepalive callbacks after idle/cleanup and ensuring Discord dispatch always marks typing idle even if preview-stream cleanup fails. (#26295) Thanks @ngutman.
- Channels/Typing indicator: guard typing keepalive start callbacks after idle/cleanup close so post-close ticks cannot re-trigger stale typing indicators. (#26325) Thanks @win4r.
- Followups/Typing indicator: ensure followup turns mark dispatch idle on every exit path (including `NO_REPLY`, empty payloads, and agent errors) so typing keepalive cleanup always runs and channel typing indicators do not get stuck after queued/silent followups. (#26881) Thanks @codexGW.
- Slack/Allowlist channels: match channel IDs case-insensitively during channel allowlist resolution so lowercase config keys (for example `c0abc12345`) correctly match Slack runtime IDs (`C0ABC12345`) under `groupPolicy: "allowlist"`, preventing silent channel-event drops. (#26878) Thanks @lbo728.
- Tests/Low-memory stability: disable Vitest `vmForks` by default on low-memory local hosts (`<64 GiB`), keep low-profile extension lane parallelism at 4 workers, and align cron isolated-agent tests with `setSessionRuntimeModel` usage to avoid deterministic suite failures. (#26324) Thanks @ngutman.
- Slack/Inbound media fallback: deliver file-only messages even when Slack media downloads fail by adding a filename placeholder fallback, capping fallback names to the shared media-file limit, and normalizing empty filenames to `file` so attachment-only messages are not silently dropped. (#25181) Thanks @justinhuangcode.

View File

@@ -96,8 +96,16 @@ export function resolveSlackChannelConfig(params: {
const keys = Object.keys(entries);
const normalizedName = channelName ? normalizeSlackSlug(channelName) : "";
const directName = channelName ? channelName.trim() : "";
// Slack always delivers channel IDs in uppercase (e.g. C0ABC12345) but
// operators commonly write them in lowercase in their config. Add both
// case variants so the lookup is case-insensitive without requiring a full
// entry-scan. buildChannelKeyCandidates deduplicates identical keys.
const channelIdLower = channelId.toLowerCase();
const channelIdUpper = channelId.toUpperCase();
const candidates = buildChannelKeyCandidates(
channelId,
channelIdLower !== channelId ? channelIdLower : undefined,
channelIdUpper !== channelId ? channelIdUpper : undefined,
channelName ? `#${directName}` : undefined,
directName,
normalizedName,

View File

@@ -60,6 +60,27 @@ describe("resolveSlackChannelConfig", () => {
matchSource: "direct",
});
});
it("matches channel config key stored in lowercase when Slack delivers uppercase channel ID", () => {
// Slack always delivers channel IDs in uppercase (e.g. C0ABC12345).
// Users commonly copy them in lowercase from docs or older CLI output.
const res = resolveSlackChannelConfig({
channelId: "C0ABC12345",
channels: { c0abc12345: { allow: true, requireMention: false } },
defaultRequireMention: true,
});
expect(res).toMatchObject({ allowed: true, requireMention: false });
});
it("matches channel config key stored in uppercase when user types lowercase channel ID", () => {
// Defensive: also handle the inverse direction.
const res = resolveSlackChannelConfig({
channelId: "c0abc12345",
channels: { C0ABC12345: { allow: true, requireMention: false } },
defaultRequireMention: true,
});
expect(res).toMatchObject({ allowed: true, requireMention: false });
});
});
const baseParams = () => ({