feat(slack): admit bot @mentions via allow_bot_ids, drop self always (closes #55)#56
Open
initializ-mk wants to merge 1 commit into
Open
feat(slack): admit bot @mentions via allow_bot_ids, drop self always (closes #55)#56initializ-mk wants to merge 1 commit into
initializ-mk wants to merge 1 commit into
Conversation
…loses #55) The Slack adapter previously dropped every event with bot_id != "", blocking legitimate integrations (scheduler, monitoring, workflow, CI bots @mentioning the agent) while a human posting the same text would trigger a response. Lifting that filter wholesale would let the agent respond to its own messages and hot-loop. This change does two things, both inside the slack adapter: - Capture the agent's own bot_id at startup. resolveBotID already called auth.test to get user_id; the same response payload includes bot_id. Store it as p.ownBotID. - Replace the binary filter with admitBotEvent(botID): humans always admitted; the agent's own bot_id is dropped unconditionally (self- loop guard, no opt-out, applies even if listed in allow_bot_ids); every other bot is admitted only when its bot_id appears in the new allow_bot_ids setting on slack-config.yaml. Default behavior is preserved — with allow_bot_ids absent, no other bots are admitted. Admitted bots still have to include <@FORGE_AGENT> in the text to trigger a response (the existing mention-matching block at slack.go:346-361 is untouched), so an admitted scheduler bot posting general chatter does not invoke the agent. Both drop paths emit an operator-actionable log line naming the bot_id and pointing at the YAML setting. Tests: - TestResolveBotID extended: bot_id from auth.test lands on p.ownBotID. - TestParseAllowBotIDs: empty / single / spaces / empty entries. - TestInit_PopulatesAllowBotIDs, TestInit_AllowBotIDsAbsent: setting flows through Init. - TestAdmitBotEvent (table-driven): human, own bot, allowlisted, non-allowlisted; reason includes "allow_bot_ids". - TestAdmitBotEvent_SelfGuardBeatsAllowlist: hard rule from #55 — even if own bot_id is misconfigured into allow_bot_ids, the self-loop guard short-circuits. Template forge-cli/templates/init/slack-config.yaml.tmpl gets a commented example of allow_bot_ids so `forge init` users can discover the option without reading the source.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #55 — the Slack adapter previously dropped every event with
bot_id != "", so a forge-agent that responded to human @-mentions stayed silent when a scheduler, monitoring tool, workflow step, or CI bot routed a mention to it. Lifting that filter naively would let the agent respond to its own messages and hot-loop.This PR replaces the binary filter with two coordinated rules:
bot_idis dropped before the allowlist is even consulted. No opt-out — even ifownBotIDis misconfigured intoallow_bot_ids, the guard short-circuits.allow_bot_idssetting onslack-config.yamladmits specific bot IDs. Empty/absent = today's behavior (no other bots).Changes
forge-plugins/channels/slack/slack.goPluginstruct: new fieldsownBotIDandallowBotIDs.resolveBotIDextended to parsebot_idfrom theauth.testresponse alongside the existinguser_id.Initparses the comma-separatedallow_bot_idssetting via new helperparseAllowBotIDs.admitBotEvent(botID) (reason, admit)encodes the three-rule decision in one testable place. The previous binary filter is replaced by a single call to this helper.bot_idand points toslack-config.yaml allow_bot_idsso debugging is self-service.forge-cli/templates/init/slack-config.yaml.tmplallow_bot_idsforforge initusers, with a pointer to where to find a bot'sbot_idin Slack admin and a note that the agent's own messages are always ignored.Why the allowlist is
bot_idrather than app namebot_idis in every event payload (payload.event.bot_id) without an extra API call, and is stable per-bot-user within a workspace. Resolving an app name toapp_idwould require a separateapps.inforound-trip. Operators can find thebot_idin Slack admin → Manage apps → app → Bot User OAuth.Loop-safety, recap
Three safeguards keep loops bounded even after admitting some bot mentions:
admitBotEvent. The non-negotiable rule from Slack adapter silently drops @-mentions from other bots #55.allow_bot_idsstay dropped, so an inadvertent loop requires an explicit operator action.slack.go:346-361(untouched) requires<@FORGE_AGENT_USER_ID>in the text even for admitted bots — chatter from an allowed scheduler bot that doesn't tag the agent goes nowhere.A rate limit on bot-authored invocations is a sensible follow-up if a scheduler misfires, but is not part of this PR.
Tests
New / updated tests in
forge-plugins/channels/slack/slack_test.go:TestResolveBotID(updated) — assertsp.ownBotIDis captured from theauth.testbot_idfield.TestParseAllowBotIDs(table-driven) — empty, single, spaces, empty entries, whitespace-only.TestInit_PopulatesAllowBotIDs— comma-separatedallow_bot_idsflows throughInit.TestInit_AllowBotIDsAbsent— default behavior preserved when the setting is absent.TestAdmitBotEvent(table-driven) — human admitted, own bot dropped, allowlisted bot admitted, non-allowlisted bot dropped; drop reason includes theallow_bot_idskeyword so operators can grep for it.TestAdmitBotEvent_SelfGuardBeatsAllowlist— explicit regression for the Slack adapter silently drops @-mentions from other bots #55 acceptance criterion that even an own-bot-id-in-allowlist misconfiguration is short-circuited by the self-loop guard.Acceptance criteria from #55
slack-config.yaml(allow_bot_ids) and the agent responds to those bots' @-mentions.bot_idis inallow_bot_ids(proven byTestAdmitBotEvent_SelfGuardBeatsAllowlist).<@FORGE_AGENT>to trigger — existing mention-matching block atslack.go:346-361untouched.app_mentionevents for admitted bots don't double-fire — theType == "app_mention"dedup atslack.go:341is unchanged, so themessageevent remains the single handler path.bot_idand pointing to the YAML setting.Test plan
slack-config.yamlcontaining noallow_bot_ids. Confirm a human @-mention still triggers a response and a second bot's @-mention is silently ignored (existing behavior preserved).chat:write-capable test bot to the workspace, note itsbot_id, add it toallow_bot_ids, restart the agent. Confirm a<@FORGE_AGENT> pingfrom that bot triggers a response identical to the human case.<@FORGE_AGENT>) does not trigger the agent.bot_idinallow_bot_ids. Confirm the agent still ignores its own messages and the log explains why.