Repository navigation
feat(slack_v2): eval-driven fixes for the MCP tool surface - #21493
Conversation
Iterated against the MCP eval suite (pd-connect-eval-monster/evals/slack_v2). Suite went 23/35 -> 33/35 on Sonnet 5 (pass^2); every change below is tied to a specific red-to-green flip or a measured payload reduction. - list-channels: evals #2/#10/#11/#16/#19/#35 all failed the same way — the action returned every field of every channel with numPages defaulting to 1, so a 62-channel workspace produced ~55k chars, blew the 25k-token MCP ceiling, and the model was handed a file path instead of data (#2 burned all 20 turns re-calling it). Added an additive `fields` projection, a `cursor` prop, and `has_more`/`next_cursor` so truncation is visible instead of silent. Payload 55k -> 5.9k chars; all six evals pass. [minor] - slack_v2.app.mjs: `assistantSearch` called `sdk().apiCall()` directly — the one path in the app that bypassed `_withRetries` — while the client is built with `rejectRateLimitedCalls: true`, so every 429 rejected instantly. 8 of 12 search calls errored in one run (#24/#26/#27). Routed through `_withRetries`. Also adds `resolveUserId` (id / email / display name) for the invite fix below. - delete-message: eval #20 ("post a note then take it back") failed every run with cant_delete_message — post-message sends no `as_user` so it posts as the USER, while this action defaulted `as_user: false`, which routes to the BOT token. A message just posted could never be deleted. Now retries with the other identity on cant_delete_message; the default is unchanged, so existing workflows are unaffected. Description also gained confirmation guidance for the destructive path (#33). [minor] - invite-user-to-channel: eval #10 failed with user_not_found — the agent passed the email from the prompt. Now resolves user id / email / display name, and the channel by name. [minor] - get-channel-details, list-members-in-channel, set-channel-topic: every AI-optimized action in this app resolves channel NAMES server-side and these did not, so agents that read "#seinfeld-general" from a prompt got channel_not_found (#19, #22) or a bare internal_error (#10). All three now use resolveChannelId. [minor] - get-channel-history, get-thread-replies: outputs measured at 17k chars average (worst 49k) and 25k average respectively — every call burned that much of the agent's context. Added the same additive `fields` projection. [minor] - get-user-details, get-current-user: two tools answer "who am I" and the agent chose the legacy one in 10/10 trials (#1). Descriptions now differentiate them. NOTE: this did NOT change routing — the agent still picks get-current-user. The actual fix is removing one of the two from the /v3 component allowlist; these edits only make the intended split legible. [patch] App package.json bumped 0.6.1 -> 0.7.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughSlack v2 adds channel and user reference resolution, selective response fields, channel pagination continuation, delete-message identity fallback, assistant-search retries, and updated action, source, and package metadata. ChangesSlack v2 action enhancements
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant SlackAction
participant SlackV2App
participant SlackAPI
SlackAction->>SlackV2App: resolve channel or user reference
SlackV2App->>SlackAPI: look up reference
SlackAPI-->>SlackV2App: return canonical ID
SlackV2App-->>SlackAction: provide resolved ID
SlackAction->>SlackAPI: execute operation
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
dylburger
left a comment
There was a problem hiding this comment.
Per-change rationale inline — each comment cites the eval that motivated it, and says plainly where a change did not have the intended effect. Full suite and report live in pd-connect-eval-monster/evals/slack_v2.
| version: "0.1.4", | ||
| description: | ||
| "Return a list of channels in a workspace." | ||
| + " **Always pass `fields`** with just the properties you need (e.g. `id,name`) —" |
There was a problem hiding this comment.
Why: six evals (#2/#10/#11/#16/#19/#35) failed the same way — with numPages defaulting to 1 and every field of every channel returned, a 62-channel workspace produced ~55k chars, over the 25k-token MCP output ceiling. Claude Code spilled it to a file and handed the model a path, so it never saw the data; one eval burned all 20 turns re-calling this action.
Change: additive fields projection, a cursor prop, and has_more/next_cursor so a partial list is visible rather than silent.
Result: 55k → 5.9k chars, all six evals pass, and the agent now passes fields:["id","name"] and raises numPages unprompted. Minor — new props and new output fields; omit fields and the response is byte-identical to today.
| return this.sdk().apiCall("assistant.search.context", { | ||
| // a method on WebClient — but it must STILL go through _withRetries. Calling | ||
| // apiCall bare was the one path in this app that skipped the retry wrapper, and | ||
| // the client is built with `rejectRateLimitedCalls: true`, so a 429 rejected |
There was a problem hiding this comment.
Why: assistantSearch called sdk().apiCall() directly — the only path in this app that bypassed _withRetries — while the client is constructed with rejectRateLimitedCalls: true, so every 429 rejected instantly instead of backing off. Slack rate-limits assistant.search.context readily (retryAfter: 60): 8 of 12 search calls errored in one run (#24/#26/#27).
Change: routed through the same _withRetries wrapper every other method already uses.
Result: no more hard 429 tool errors.
| version: "0.1.4", | ||
| description: | ||
| "Permanently delete a message. **This cannot be undone — confirm the exact message with the" | ||
| + " user before calling it.** Identify the message first with **Get Channel History** or" |
There was a problem hiding this comment.
Why: eval #20 ("post a note then take it back") failed every run with cant_delete_message. post-message sends no as_user, so it posts as the USER; this action defaulted as_user: false, which makeRequest routes to the BOT token. A message the agent had just posted could never be deleted.
Change: try the configured identity, fall back to the other one on cant_delete_message. The default is untouched, so existing workflows behave exactly as before. The description also gained confirmation guidance for the destructive path (#33).
Result: #20 and #33 pass. Minor — backwards-compatible behaviour improvement.
| version: "0.0.29", | ||
| description: "Invite a user to an existing channel. Accepts a channel ID or NAME, and a user ID, EMAIL address or display name — all resolved automatically. [See the documentation](https://api.slack.com/methods/conversations.invite)", | ||
| version: "0.1.0", | ||
| annotations: { |
There was a problem hiding this comment.
Why: eval #10 failed with user_not_found — asked to invite dylan@pipedream.com, the agent passed exactly that, and the raw API wants a user id.
Change: new resolveUserId app method (id passthrough / users.lookupByEmail / display-name scan), plus channel-name resolution.
Result: #10 passes. Minor — accepts more identifier shapes, rejects none.
| version: "0.0.5", | ||
| description: "Retrieve details for a Slack channel by selecting it or providing an ID. Accepts a channel ID or NAME — names are resolved automatically. [See the documentation](https://api.slack.com/methods/conversations.info)", | ||
| version: "0.1.0", | ||
| type: "action", |
There was a problem hiding this comment.
| version: "0.0.29", | ||
| description: "Set the topic on a selected channel. Accepts a channel ID or NAME — names are resolved automatically. [See the documentation](https://api.slack.com/methods/conversations.setTopic)", | ||
| version: "0.1.0", | ||
| annotations: { |
| + " Use this when you want to see a channel's latest messages — unlike **Search** which finds messages by keyword." | ||
| + " Returns messages with text, timestamps (ts), reactions, and user IDs." | ||
| + " Message timestamps can be used with **Get Thread Replies**, **Edit Message**, and **Add Reaction**." | ||
| + " **Pass `fields`** (e.g. `text,ts,user`) unless you need full message objects — raw Slack" |
There was a problem hiding this comment.
Why: outputs measured at 17k chars average, worst case 49k — every call burned that much of the agent's context and degraded the rest of the task.
Change: additive fields projection (text,ts,user covers most tasks).
Result: a channel-summary eval now returns 1.6k chars instead of tens of thousands. Minor — omit fields and the response is byte-identical to today.
| + " Accepts a channel ID or channel name (resolved automatically)." | ||
| + " Use **Get Channel History** or **Search** to find the parent message's timestamp (thread_ts)." | ||
| + " Returns the parent message followed by all replies in chronological order." | ||
| + " **Pass `fields`** (e.g. `text,ts,user`) unless you need full message objects — raw" |
There was a problem hiding this comment.
Why: outputs averaged 25k chars (worst 35k) on a long thread — raw Slack messages carry blocks, attachments and edit metadata.
Change: the same additive fields projection.
Result: thread reads stay well under the output ceiling. Minor.
| description: | ||
| "Retrieve the authenticated user's identity and workspace context." | ||
| "**Use when the user asks who am I, what's my user ID, what's my username, which workspace" | ||
| + " am I in, or what's my email** — this is the default identity lookup and the one to call" |
There was a problem hiding this comment.
Why: eval #1 ("who am I in Slack?") pins this tool, and the agent chose the legacy get-current-user in 10/10 trials across 7 runs.
Change: the description now leads with the phrasings users actually type and defers the full-profile case to get-current-user.
Result — honest caveat: this did NOT change routing. The agent still picks get-current-user. The tool NAME is the retrieval key under tool search, and "Get Current User" simply matches "who am I" better than "Get User Details"; no description tuning arbitrates two tools that answer one question. The real fix is dropping one of the two from the /v3 component allowlist — this edit only makes the intended split legible. Patch — description only.
| version: "0.0.4", | ||
| description: "Do NOT use for a plain \"who am I\" / \"what's my user ID\" question — call **Get User Details** for that; it answers the same question with a far smaller payload. Use this ONLY when you specifically need the full member profile: locale, timezone, presence/status, admin and owner flags, name variants, or workspace domain and enterprise metadata. Combines `auth.test`, `users.info`, `users.profile.get` and `team.info`. [See Slack API docs](https://api.slack.com/methods/auth.test).", | ||
| version: "0.0.5", | ||
| type: "action", |
There was a problem hiding this comment.
Why: paired with the get-user-details change above — two tools answer one question and the agent cannot arbitrate between them.
Change: the description now says explicitly not to use this for a plain "who am I", and scopes it to the full-profile case (locale, presence, admin flags).
Result: no measured routing change — see the note on get-user-details. Patch — description only.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@components/slack_v2/actions/get-channel-details/get-channel-details.mjs`:
- Line 6: Update the description at
components/slack_v2/actions/get-channel-details/get-channel-details.mjs:6 to
remove “by selecting it or providing an ID” and describe the channel input
directly by ID or name. Also update
components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs:6 to replace
“selected channel” with equivalent channel-ID-or-name wording, avoiding
UI-centric language in both descriptions.
In `@components/slack_v2/actions/list-channels/list-channels.mjs`:
- Line 117: Resolve the ESLint formatting violations in the field-splitting
expressions around the ternary branches at the referenced locations, including
the corresponding expression near the later occurrence. Reformat the chained
split, trim, and filter calls according to the project’s lint rules without
changing their behavior.
In `@components/slack_v2/slack_v2.app.mjs`:
- Line 1230: Fix the reported lint formatting in all three sites: expand the
destructuring declaration around makeRequest in
components/slack_v2/slack_v2.app.mjs (1230-1230) across multiple lines, and move
the chained .filter(Boolean) calls onto their own lines in
components/slack_v2/actions/get-channel-history/get-channel-history.mjs (82-82)
and components/slack_v2/actions/get-thread-replies/get-thread-replies.mjs
(75-75), without changing behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 92a797b1-8dc0-40e1-88ae-6645cfc1e145
📒 Files selected for processing (12)
components/slack_v2/actions/delete-message/delete-message.mjscomponents/slack_v2/actions/get-channel-details/get-channel-details.mjscomponents/slack_v2/actions/get-channel-history/get-channel-history.mjscomponents/slack_v2/actions/get-current-user/get-current-user.mjscomponents/slack_v2/actions/get-thread-replies/get-thread-replies.mjscomponents/slack_v2/actions/get-user-details/get-user-details.mjscomponents/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjscomponents/slack_v2/actions/list-channels/list-channels.mjscomponents/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjscomponents/slack_v2/actions/set-channel-topic/set-channel-topic.mjscomponents/slack_v2/package.jsoncomponents/slack_v2/slack_v2.app.mjs
… UI-centric copy
- Lint Code Base: 9 errors, all in the files this branch touches and all
formatting. Broke the `.split().map().filter()` chains across lines
(newline-per-chained-call), split the `has_more` spread ternary and its object
literal (multiline-ternary, object-curly-newline), and expanded the
`users.list` destructure in resolveUserId (object-curly-newline).
- Ensure component commits modify component versions: this branch edits
slack_v2.app.mjs, so every component importing it needs a version bump.
Patch-bumped the 48 actions/sources that were not already versioned in the
previous commit.
- Removed UI-centric wording from two descriptions, which read oddly for an
agent calling the tool over MCP and contradicted the resolution behaviour
added in the previous commit:
- get-channel-details: "by selecting it or providing an ID" -> "specified by
ID or by name" (it had claimed both "selecting it" and "Accepts a channel
ID or NAME" in the same sentence).
- set-channel-topic: "a selected channel" -> "a channel, specified by ID or
by name".
Verified locally with ESLint 9 against the repo's own rule options for the three
failing rules: 0 violations across components/slack_v2.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/slack_v2/slack_v2.app.mjs (1)
1219-1219: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftDo not treat every ID-shaped token as a confirmed Slack user ID.
The case-insensitive regex returns values such as
user1234immediately, so a valid exact name with that shape never reaches the paginated name lookup. Validate the ID against Slack (or fall back to name resolution when it is not confirmed) before returning it. Slack documentsU/Wprefixes but cautions against assuming a complete ID format. (api.slack.com)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/slack_v2/slack_v2.app.mjs` at line 1219, Update the input resolution logic around the /^[UW].../ check so ID-shaped tokens are not returned immediately. Confirm candidate IDs through Slack before returning them, and fall back to the existing paginated name lookup when validation fails, preserving direct handling for confirmed user IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@components/slack_v2/slack_v2.app.mjs`:
- Line 1219: Update the input resolution logic around the /^[UW].../ check so
ID-shaped tokens are not returned immediately. Confirm candidate IDs through
Slack before returning them, and fall back to the existing paginated name lookup
when validation fails, preserving direct handling for confirmed user IDs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7abda907-7f2d-4cfe-9e02-37dc8ba49b7c
📒 Files selected for processing (54)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjscomponents/slack_v2/actions/add-reaction/add-reaction.mjscomponents/slack_v2/actions/approve-workflow/approve-workflow.mjscomponents/slack_v2/actions/archive-channel/archive-channel.mjscomponents/slack_v2/actions/browse-files/browse-files.mjscomponents/slack_v2/actions/create-channel/create-channel.mjscomponents/slack_v2/actions/create-reminder/create-reminder.mjscomponents/slack_v2/actions/delete-file/delete-file.mjscomponents/slack_v2/actions/edit-message/edit-message.mjscomponents/slack_v2/actions/find-message/find-message.mjscomponents/slack_v2/actions/find-user-by-email/find-user-by-email.mjscomponents/slack_v2/actions/find-user-by-id/find-user-by-id.mjscomponents/slack_v2/actions/get-channel-details/get-channel-details.mjscomponents/slack_v2/actions/get-channel-history/get-channel-history.mjscomponents/slack_v2/actions/get-file/get-file.mjscomponents/slack_v2/actions/get-thread-replies/get-thread-replies.mjscomponents/slack_v2/actions/kick-user/kick-user.mjscomponents/slack_v2/actions/list-channels/list-channels.mjscomponents/slack_v2/actions/list-emojis/list-emojis.mjscomponents/slack_v2/actions/list-files/list-files.mjscomponents/slack_v2/actions/list-group-members/list-group-members.mjscomponents/slack_v2/actions/list-icon-emoji-options/list-icon-emoji-options.mjscomponents/slack_v2/actions/list-messages/list-messages.mjscomponents/slack_v2/actions/list-reminder-options/list-reminder-options.mjscomponents/slack_v2/actions/list-replies/list-replies.mjscomponents/slack_v2/actions/list-user-group-options/list-user-group-options.mjscomponents/slack_v2/actions/list-users/list-users.mjscomponents/slack_v2/actions/post-message/post-message.mjscomponents/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjscomponents/slack_v2/actions/search/search.mjscomponents/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjscomponents/slack_v2/actions/send-large-message/send-large-message.mjscomponents/slack_v2/actions/send-message-advanced/send-message-advanced.mjscomponents/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjscomponents/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjscomponents/slack_v2/actions/send-message/send-message.mjscomponents/slack_v2/actions/set-channel-description/set-channel-description.mjscomponents/slack_v2/actions/set-channel-topic/set-channel-topic.mjscomponents/slack_v2/actions/set-status/set-status.mjscomponents/slack_v2/actions/update-group-members/update-group-members.mjscomponents/slack_v2/actions/update-message/update-message.mjscomponents/slack_v2/actions/update-profile/update-profile.mjscomponents/slack_v2/actions/upload-file/upload-file.mjscomponents/slack_v2/actions/verify-slack-signature/verify-slack-signature.mjscomponents/slack_v2/slack_v2.app.mjscomponents/slack_v2/sources/new-channel-created/new-channel-created.mjscomponents/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjscomponents/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjscomponents/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjscomponents/slack_v2/sources/new-private-channel-created/new-private-channel-created.mjscomponents/slack_v2/sources/new-reaction-added/new-reaction-added.mjscomponents/slack_v2/sources/new-saved-message/new-saved-message.mjscomponents/slack_v2/sources/new-user-added/new-user-added.mjscomponents/slack_v2/sources/new-user-mention/new-user-mention.mjs
michelle0927
left a comment
There was a problem hiding this comment.
Just one minor comment.
|
Hi @dylburger, this has a merge conflict to resolve. |
…entity fallback invite-user-to-channel: conversations.invite documents `users` as comma-separated and the prop is a plain string, so "U01ABCDEF,U02GHIJKL" was a working input. The new resolveUserId() matches neither the ID regex (no comma) nor the email regex, then falls through to an exhaustive paged users.list scan that cannot match, and throws. Adds resolveUserIds() (plural), which splits on commas — or accepts an array — resolves each token independently, and rejoins. An all-IDs input still makes zero extra API calls, so the common path is unchanged in cost as well as result. delete-message: makeRequest() routes to the bot token only on `as_user === false`, so `!this.as_user` made the retry a no-op whenever as_user arrived nullish — undefined and true both route to the user token, so it retried as the identity that had just been refused. Changed to `this.as_user === false`. Attempt 1 still passes the configured value verbatim. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # components/slack_v2/actions/approve-workflow/approve-workflow.mjs
resolveUserId/resolveUserIds were inserted between resolveChannelId's doc comment and resolveChannelId itself, leaving two stacked doc blocks above resolveUserId and no doc on resolveChannelId. Comment move only; no code change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review feedback on #21493: `pickFields` was copy-pasted identically into get-channel-history, get-thread-replies and list-channels. Moved to components/slack_v2/common/utils.mjs, alongside the CSV-or-array `fields` normalizer that was duplicated in the same three places — both halves of one feature, so splitting them across files would just invite the next divergence. `projectFields(records, fields)` composes the two and keeps the additive contract: with no `fields`, it returns the ORIGINAL array, so a caller that omits the prop gets exactly what these actions have always returned. Behavior-identical to the three removed copies, verified against them across undefined / empty array / empty string / array / CSV / CSV-with-spaces / unknown field / all-unknown inputs, including array identity on the no-fields path. No version bumps: all three components are already ahead of master on this branch, so the PR diff still shows a version change for each. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…o mcp/slack_v2
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
39-49: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winPreserve failures for bulk invitations.
When one resolved user is already in the channel, Slack rejects the bulk request and does not invite the remaining users. The catch at lines 46–49 reports this failed request as successful. Handle
already_in_channelas success only for a single-user request, or invite users individually and report each outcome.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs` around lines 39 - 49, Update the error handling around inviteToConversation in the action so already_in_channel is treated as success only when the request contains a single user. For bulk invitations, preserve the failure or invite users individually and report each result, ensuring remaining users are not hidden behind a successful summary; use the existing channel, user, and summary flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@components/slack_v2/actions/delete-message/delete-message.mjs`:
- Around line 62-69: Update the retry logic around makeRequest’s attempt calls
to check bot-token availability before falling back from the initial as_user
value. Only call attempt(other) when a bot token exists and the alternate
identity differs; otherwise rethrow the original cant_delete_message error
without making a second request.
In `@components/slack_v2/actions/get-current-user/get-current-user.mjs`:
- Around line 7-10: Update the action description and related metadata for the
get-current-user action to remove the instruction to call it first in every
session. Scope its usage explicitly to requests requiring Slack identity data,
while preserving the existing identity lookup behavior.
In `@components/slack_v2/slack_v2.app.mjs`:
- Around line 1225-1230: Update resolveUserId(), used by resolveUserIds(), to
collect every exact match across all users.list pages instead of returning the
first match. Return the ID only when exactly one account matches; throw
ConfigurationError for ambiguous name matches and require callers to use an ID
or email, while preserving the existing no-match behavior.
---
Outside diff comments:
In
`@components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs`:
- Around line 39-49: Update the error handling around inviteToConversation in
the action so already_in_channel is treated as success only when the request
contains a single user. For bulk invitations, preserve the failure or invite
users individually and report each result, ensuring remaining users are not
hidden behind a successful summary; use the existing channel, user, and summary
flow.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: e633c6ce-2140-4646-867b-bdf700c44d6e
📒 Files selected for processing (60)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjscomponents/slack_v2/actions/add-reaction/add-reaction.mjscomponents/slack_v2/actions/archive-channel/archive-channel.mjscomponents/slack_v2/actions/browse-files/browse-files.mjscomponents/slack_v2/actions/create-channel/create-channel.mjscomponents/slack_v2/actions/create-reminder/create-reminder.mjscomponents/slack_v2/actions/delete-file/delete-file.mjscomponents/slack_v2/actions/delete-message/delete-message.mjscomponents/slack_v2/actions/edit-message/edit-message.mjscomponents/slack_v2/actions/find-message/find-message.mjscomponents/slack_v2/actions/find-user-by-email/find-user-by-email.mjscomponents/slack_v2/actions/find-user-by-id/find-user-by-id.mjscomponents/slack_v2/actions/get-channel-details/get-channel-details.mjscomponents/slack_v2/actions/get-channel-history/get-channel-history.mjscomponents/slack_v2/actions/get-current-user/get-current-user.mjscomponents/slack_v2/actions/get-file/get-file.mjscomponents/slack_v2/actions/get-thread-replies/get-thread-replies.mjscomponents/slack_v2/actions/get-user-details/get-user-details.mjscomponents/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjscomponents/slack_v2/actions/kick-user/kick-user.mjscomponents/slack_v2/actions/list-channels/list-channels.mjscomponents/slack_v2/actions/list-emojis/list-emojis.mjscomponents/slack_v2/actions/list-files/list-files.mjscomponents/slack_v2/actions/list-group-members/list-group-members.mjscomponents/slack_v2/actions/list-icon-emoji-options/list-icon-emoji-options.mjscomponents/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjscomponents/slack_v2/actions/list-messages/list-messages.mjscomponents/slack_v2/actions/list-reminder-options/list-reminder-options.mjscomponents/slack_v2/actions/list-replies/list-replies.mjscomponents/slack_v2/actions/list-user-group-options/list-user-group-options.mjscomponents/slack_v2/actions/list-users/list-users.mjscomponents/slack_v2/actions/post-message/post-message.mjscomponents/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjscomponents/slack_v2/actions/search/search.mjscomponents/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjscomponents/slack_v2/actions/send-large-message/send-large-message.mjscomponents/slack_v2/actions/send-message-advanced/send-message-advanced.mjscomponents/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjscomponents/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjscomponents/slack_v2/actions/send-message/send-message.mjscomponents/slack_v2/actions/set-channel-description/set-channel-description.mjscomponents/slack_v2/actions/set-channel-topic/set-channel-topic.mjscomponents/slack_v2/actions/set-status/set-status.mjscomponents/slack_v2/actions/update-group-members/update-group-members.mjscomponents/slack_v2/actions/update-message/update-message.mjscomponents/slack_v2/actions/update-profile/update-profile.mjscomponents/slack_v2/actions/upload-file/upload-file.mjscomponents/slack_v2/actions/verify-slack-signature/verify-slack-signature.mjscomponents/slack_v2/common/utils.mjscomponents/slack_v2/package.jsoncomponents/slack_v2/slack_v2.app.mjscomponents/slack_v2/sources/new-channel-created/new-channel-created.mjscomponents/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjscomponents/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjscomponents/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjscomponents/slack_v2/sources/new-private-channel-created/new-private-channel-created.mjscomponents/slack_v2/sources/new-reaction-added/new-reaction-added.mjscomponents/slack_v2/sources/new-saved-message/new-saved-message.mjscomponents/slack_v2/sources/new-user-added/new-user-added.mjscomponents/slack_v2/sources/new-user-mention/new-user-mention.mjs
…Id, invite-user-to-channel - delete-message: only retry with the other identity when a bot token exists, since otherwise both attempts hit the same token and just repeat the error. - resolveUserId: scan all users.list pages and require an exact, unambiguous name match; throw ConfigurationError instead of silently picking the first. - invite-user-to-channel: treat already_in_channel as success only for a single user; bulk invites are now sent per-user with individual results so failures aren't hidden behind a successful summary. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs`:
- Line 39: Update the user resolution flow around resolveUserIds and the userIds
assignment to remove empty comma-separated tokens before building the Slack
request. When no resolved user IDs remain, throw ConfigurationError for the
invalid input instead of sending an empty users value; preserve normal behavior
for valid IDs.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8804d5e9-e06c-4f0f-997f-ae34966e9554
📒 Files selected for processing (3)
components/slack_v2/actions/delete-message/delete-message.mjscomponents/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjscomponents/slack_v2/slack_v2.app.mjs
…er list
resolveUserIds() returns "" when every comma-separated token is blank, and
"".split(",") yields [""] rather than [], which slipped through the
single-user path and sent an empty users value to Slack. Filter empty tokens
and throw ConfigurationError when none remain.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
michelle0927
left a comment
There was a problem hiding this comment.
LGTM! Ready for QA.
…ite as no-op - delete-message now calls resolveChannelId() before deleting, like every other AI-optimized action in this app. Previously it passed `conversation` straight through, so a channel NAME (rather than ID) failed with channel_not_found. - invite-user-to-channel: Slack refuses to let an identity invite itself (cant_invite_self) before it would ever reach an already_in_channel check. Treat that the same way — a no-op, not an error — since to the caller it means the same thing: the user has no further action needed on that channel. Found via eval-driven regression testing on PR #21493.
…to-channel Keep at 0.2.0/0.1.0 rather than bumping per fix.

What & why
Fixes to the AI-optimized
slack_v2tool surface, driven by the MCP eval suite inpd-connect-eval-monster/evals/slack_v2. The suite went 23/35 → 33/35 on Sonnet 5(pass²); every change below is tied to a red-to-green flip or a measured payload cut.
Changes (per component)
0.1.4→0.2.0, minor) — returned every field of every channel withnumPagesdefaulting to 1, so a 62-channel workspace produced ~55k chars, exceeded the25k-token MCP output ceiling, and the model received a file path instead of the data (one
eval burned all 20 turns re-calling it). Added an additive
fieldsprojection, acursorprop, and
has_more/next_cursorso truncation is visible instead of silent. Payload55k → 5.9k chars; unblocked evals Remove JSON.parse from default http component #2/Update default timer to 15 mins #10/updates to props and emit summary #11/Polish updates #16/Jotform #19/Add HTTP response for all webhook requests #35.
assistantSearchcalledsdk().apiCall()directly, the one path inthe app that bypassed
_withRetries, while the client is constructed withrejectRateLimitedCalls: true— so every 429 rejected instantly. 8 of 12 search callserrored in one run (Trying yet again #24/Fauna #26/Update rss.js #27). Now routed through
_withRetries. Also addsresolveUserId(id / email / display name) for the invite fix below.0.1.4→0.2.0, minor) —post-messagesends noas_userso it postsas the USER, while this action defaulted
as_user: false, which routes to the BOT token. Amessage just posted could never be deleted (Pipedream SQL Source #20,
cant_delete_message). Now retries with theother identity on that error; the default is unchanged so existing workflows are unaffected.
Description also gained confirmation guidance for the destructive path (GitHub #33).
0.0.29→0.1.0, minor) — failed withuser_not_foundwhen theagent passed the email from the prompt (Update default timer to 15 mins #10). Now resolves user id / email / display name,
and the channel by name.
0.1.0, minor) — everyAI-optimized action in this app resolves channel NAMES server-side and these did not, so an
agent that read
#seinfeld-generalfrom a prompt gotchannel_not_found(Jotform #19, safely get oauth tokens #22) or a bareinternal_error(Update default timer to 15 mins #10). All three now useresolveChannelId.0.1.0, minor) — outputs measured at 17k charsaverage (worst 49k) and 25k average respectively; every call burned that much of the agent's
context. Added the same additive
fieldsprojection.change routing — the agent still picks
get-current-userin every trial. The actual fix isremoving one of the two from the
/v3component allowlist; these edits only make theintended split legible.
All payload changes are additive: omit
fieldsand each action returns exactly what itreturns today.
Verification
pass). Report:
evals/slack_v2/results/2026-07-25T00-21-03-010Z.report.md.component bug) and Fauna #26 (flaky — Slack rate-limits
assistant.search.contextunder high evalconcurrency; the retry fix here addresses the hard-error case, not the rate limit itself).
node_modules) and a Haiku confirming pass.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation