feat: multi-workspace Slack support#195
Conversation
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds multi-workspace Slack support via a Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant prefs as Preferences\n(JSON)
participant env as Environment\nVariables
participant script as ops-slack-workspaces\n(Script)
participant api as Slack API\n(auth.test)
User->>script: Run ops-slack-workspaces [--json]
script->>prefs: Load slack_workspaces[]
rect rgba(200,150,100,0.5)
loop For each workspace
script->>env: Resolve token via token_env
alt Token present
script->>api: POST auth.test
alt Valid token
api-->>script: OK (team_id, url)
else Invalid token
api-->>script: Error
end
else Missing token
script->>script: Mark token_missing
end
end
end
alt --json
script-->>User: JSON array of per-workspace results
else Default
script-->>User: Human-readable table/status
end
script-->>User: Exit code (0 if all valid / legacy OK)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
Adds slack_workspaces[] to preferences.json schema. ops-unread, ops-go,
ops-inbox, ops-comms now iterate ALL configured workspaces and aggregate
results with per-workspace labels (e.g. Slack/lifecycle, Slack/stagery).
Backwards-compatible: no slack_workspaces falls back to legacy
SLACK_MCP_ENABLED=true single-workspace path.
New bin/ops-slack-workspaces helper lists configured workspaces and
smoke-tests each token via auth.test. Setup wizard step 3d now persists
workspaces into the array with a loop ("add another?") and stores tokens
in named env vars — never in preferences.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3770121 to
496f5ac
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@claude-ops/bin/ops-slack-workspaces`:
- Around line 22-26: The current human-output branch that sets ANSI colors (the
if using [[ -t 1 && $JSON_MODE -eq 0 ]]) must detect mobile/SSH sessions and
switch to a compact plain-text mode; add a mobile detection boolean (check
$SSH_CONNECTION, $SSH_CLIENT, $SSH_TTY, $OPS_MOBILE=1, or COLUMNS<80) and when
true set ANSI vars (GREEN/RED/YELLOW/RESET) to empty and set a new flag (e.g.,
COMPACT_MOBILE=1); then update the downstream rendering code referenced around
the human-output path and the sections noted (the output logic around lines
112-167) to, when COMPACT_MOBILE=1, emit short line-based plain text (no
banners, tables, emoji/glyph columns or ANSI) instead of the wide table/banner
formatting while preserving the JSON branch ($JSON_MODE) unchanged.
- Around line 71-72: The indirect expansion using "${!token_env:-}" can trigger
a "bad substitution" if token_env contains characters invalid for shell
identifiers; before performing indirect expansion (where token_env is read to
set ws_token), validate token_env against a POSIX variable-name pattern (e.g.,
start with letter/underscore, followed by letters/digits/underscores). If
validation fails, treat it as a configuration error: log a clear message and
skip expansion or exit with non-zero status; otherwise perform the indirect
expansion. Apply this validation for the token_env use that sets ws_token and
the other identical occurrence later in the file.
In `@claude-ops/bin/ops-unread`:
- Around line 80-83: The example preferences.json uses real-looking workspace
names in the "slack_workspaces" array (the entries with "name": "lifecycle" and
"name": "stagery" and their corresponding "token_env" values); update those
example objects to use neutral placeholders (e.g., "name": "workspace_name" or
"name": "<WORKSPACE>", and token_env values like "SLACK_BOT_TOKEN_<WORKSPACE>"
or "<YOUR_TOKEN_ENV>") so no real org/project identifiers are committed, keeping
the JSON keys "slack_workspaces", "name", "token_env", and "kind" intact.
- Around line 107-110: The script currently uses indirect expansion
"${!token_env}" (resolving into ws_token) without validating token_env, which
causes "invalid variable name" under set -euo pipefail for values like
"SLACK-BOT-TOKEN"; before dereferencing token_env validate it matches a
POSIX/Bash identifier (e.g. regex ^[a-zA-Z_][a-zA-Z0-9_]*$), and if it fails do
not attempt "${!token_env}" — instead emit an error record (write an error
message to stderr / log) and leave ws_token empty or handle the error path;
update the resolution logic surrounding token_env and ws_token to perform this
check before indirect expansion.
In `@claude-ops/skills/ops-comms/SKILL.md`:
- Around line 172-175: The skill currently filters workspaces by an `available`
flag on `slack_workspaces[]`, but that array only contains persisted metadata
and not availability; instead read the derived availability from
`channels.slack.workspaces[]` (as built by claude-ops/bin/ops-unread around
lines 76–137) or resolve each workspace's `token_env` to check presence before
filtering; update the workspace iteration to use `channels.slack.workspaces[]`
(or a resolved-token check) when deciding to call
`mcp__claude_ai_Slack__slack_search_public_and_private` (or do a direct curl for
non-bound workspaces), and keep the existing fallback logic for 0 workspaces /
legacy mode using `SLACK_MCP_ENABLED`.
In `@claude-ops/skills/ops-go/SKILL.md`:
- Around line 206-213: The workflow documents use the MCP tool
mcp__claude_ai_Slack__slack_search_public_and_private but the skill's
allowed-tools list doesn't expose any Slack tools, so /ops:go cannot run the
multi-workspace scan; update the SKILL.md allowed-tools section to include the
Slack MCP entries (e.g., mcp__claude_ai_Slack__slack_search_public_and_private
and any related mcp__claude_ai_Slack__* methods used by the flow) or explicitly
delegate Slack scanning to an existing skill that already lists those Slack
tools—ensure the allowed-tools list and any tool capability descriptions
reference the exact MCP symbols named in the workflow so the runtime can invoke
them.
In `@claude-ops/skills/ops-inbox/SKILL.md`:
- Around line 259-263: The Slack inbox section in SKILL.md references state and
MCP tools this skill doesn't have; change the flow to consume the pre-gathered
channels.slack object produced by bin/ops-unread (use channels.slack as the
source of truth instead of slack_workspaces[]/available/multi_workspace state)
and either add the required Slack MCP tools (mcp__claude_ai_Slack__* entries) to
this skill's allowed-tools or route Slack handling to the existing skill that
already exposes those tools; update the steps that mention
mcp__claude_ai_Slack__*, slack_workspaces[], and SLACK_MCP_ENABLED to instead
describe reading channels.slack and delegating to the tool-equipped skill or
include instructions to add the MCP tools to allowed-tools.
In `@claude-ops/skills/setup/SKILL.md`:
- Around line 1398-1429: The setup flow currently writes slack_workspaces[] to
prefs before the required shell/profile env persistence and Claude MCP
registration are completed; change the wizard in SKILL.md so that the steps run
non-interactively from the wizard (use the Bash tool to export/persist the env
var, add the keychain entry, and perform the Claude MCP registration via the
described commands) and only append to slack_workspaces[] after those commands
return success, or alternatively delay persisting slack_workspaces[] until a
successful MCP registration completes; update references to
bin/ops-slack-workspaces, the prefs key slack_workspaces[], and the MCP
registration step so the code path enforces running the commands
programmatically (background long-running tasks with run_in_background: true)
rather than asking the user to run them manually.
- Around line 1392-1439: The persisted token shape in slack_workspaces[] must
match the auth used by the smoke test (curl with Authorization: Bearer
${TOKEN}); update the persistence logic that writes to slack_workspaces[] and
the SLACK_BOT_TOKEN_<NAME> variable so you either (A) extract and persist a
direct Web API token (xoxb/xoxp/xapp) instead of the browser xoxc token when
building the entry, or (B) persist a structured session object (e.g. store both
xoxc and xoxd as token_session:{xoxc: "...", xoxd: "..."} in slack_workspaces[])
and update the smoke-test and any curl/fallback callers to use both
Authorization: Bearer ${xoxc} and the d=${xoxd} cookie; ensure the jq append
step that writes slack_workspaces[] and the helper bin/ops-slack-workspaces
logic reflect the chosen format and the printed instructions reference the
correct env-var/name semantics (SLACK_BOT_TOKEN_<NAME> for direct tokens or a
session-specific env/key if storing pairs).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 28df6e65-190a-4c0e-9b10-9c1ca32e45d0
📒 Files selected for processing (9)
claude-ops/CHANGELOG.mdclaude-ops/bin/ops-slack-workspacesclaude-ops/bin/ops-statusclaude-ops/bin/ops-unreadclaude-ops/skills/ops-comms/SKILL.mdclaude-ops/skills/ops-dash/SKILL.mdclaude-ops/skills/ops-go/SKILL.mdclaude-ops/skills/ops-inbox/SKILL.mdclaude-ops/skills/setup/SKILL.md
Sentry (MEDIUM):
- ops-go SKILL.md: replace ambiguous ${TOKEN_ENV_VALUE} placeholder with
explicit two-step env-var resolution snippet that validates token_env is
a legal shell identifier and dereferences with ${!token_env}.
CodeRabbit (MAJOR):
- ops-unread + ops-slack-workspaces: guard ${!token_env} indirect expansion
with [A-Za-z_][A-Za-z0-9_]* validation. Without this, a token_env value
containing hyphens (e.g. SLACK-BOT-TOKEN-X) would crash bash under
set -uo pipefail with 'invalid variable name', blanking the whole unread
payload. Now emits an explicit error record per workspace and continues.
Verified with mixed-workspace fixture.
- ops-unread: replace concrete workspace names (lifecycle, stagery) in the
schema docstring with <workspace_a>/<workspace_b> placeholders per
claude-ops/**/* coding guideline (no real org/project identifiers).
- ops-slack-workspaces: add IS_MOBILE detection (OPS_MOBILE=1 /
SSH_CONNECTION / SSH_CLIENT / SSH_TTY / COLUMNS<80) and switch to
compact line-based output without tables/banners/ANSI in mobile/SSH
mode, per repo formatting rule. Verified in 4 modes (JSON, mobile,
SSH, narrow terminal).
- ops-comms + ops-inbox SKILL.md: read derived channels.slack.workspaces[]
from bin/ops-unread output (which resolves token_env and emits
per-workspace 'available'), NOT raw preferences.json -> slack_workspaces[]
(which has no 'available' field — only persisted metadata). Without
this, every workspace would look unavailable and Slack scans would be
silently skipped.
- ops-go + ops-inbox SKILL.md: add Slack MCP tools to allowed-tools
(mcp__claude_ai_Slack__slack_search_public_and_private,
mcp__claude_ai_Slack__slack_read_channel). Without these the workflows
documented for /ops:go and /ops:inbox could not execute.
- setup SKILL.md (xoxc + d cookie): document that browser-session tokens
(xoxc-*) REQUIRE the companion d=xoxd-* cookie for slack.com/api/* —
Bearer-only requests return {ok:false,error:not_authed}. Adds a separate
curl example that includes -b 'd=${XOXD_TOKEN}' for xoxc tokens, and a
new kind 'xoxc_no_cookie' to mark workspaces with the bare token but no
cookie so direct-curl scans don't silently fail.
- setup SKILL.md (delegate-to-user): the 'wire into Claude Code plugin
settings' step previously printed instructions and asked the user to
edit their shell profile + run claude mcp add manually. Replaced with a
flow that performs both via the Bash tool (writes to ~/.zshrc/.bashrc
idempotently with grep -qF guard, runs claude mcp add directly) and only
appends to slack_workspaces[] AFTER persistence + MCP registration
succeed (rollback on failure with [Retry]/[Skip]/[Abort] AskUserQuestion)
so the configured-but-unusable state never persists.
All shell scripts pass bash -n syntax check; no NEW shellcheck warnings
introduced (pre-existing SC2034/SC2059/SC2086/SC2317 unchanged).
|
All 10 review-thread findings addressed in commit 9f2f95d. Per-finding resolution:
Verification:
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.
Reviewed by Cursor Bugbot for commit 9f2f95d. Configure here.
|
|
||
| SLACK_WS_COUNT=0 | ||
| SLACK_WS_NAMES=() | ||
| SLACK_WS_AVAILABLE=() |
There was a problem hiding this comment.
Unused arrays declared but never read or populated
Low Severity
SLACK_WS_NAMES=() and SLACK_WS_AVAILABLE=() are declared but never populated or read anywhere in the script. The implementation uses the WS_ARRAY JSON string approach instead, making these arrays dead code — likely leftovers from an earlier design iteration that was refactored out.
Reviewed by Cursor Bugbot for commit 9f2f95d. Configure here.
| "https://slack.com/api/auth.test" 2>/dev/null | ||
| } | ||
|
|
||
| _jq() { command -v jq >/dev/null 2>&1 && jq -r "$@" 2>/dev/null || echo ""; } |
There was a problem hiding this comment.
Helper function _jq defined but never called
Low Severity
The _jq() helper function is defined but never invoked anywhere in the script. All jq usage throughout ops-slack-workspaces calls jq directly (e.g., lines 53, 76–78, 86, 96, 104–106, etc.). This is dead code that adds unnecessary clutter.
Reviewed by Cursor Bugbot for commit 9f2f95d. Configure here.
| curl -s --max-time 5 \ | ||
| -H "Authorization: Bearer $token" \ | ||
| "https://slack.com/api/auth.test" 2>/dev/null | ||
| } |
There was a problem hiding this comment.
Validation tool ignores kind for xoxc cookie-based tokens
Medium Severity
The _test_token function always uses Bearer-only auth, but the setup SKILL.md (same PR) documents that xoxc tokens require a companion d=xoxd-… cookie — without it, Slack returns {"ok":false,"error":"not_authed"}. The script loads ws_kind per workspace but never branches on it to adjust the auth method. Workspaces configured via the browser-extraction path (kind: "xoxc_with_cookie") will always report as invalid, producing false failures. The setup SKILL.md at line 1464 suggests this helper as the verification step, creating a confusing post-setup experience.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9f2f95d. Configure here.
…release (#199) The v2.0.5 → v2.0.9 patch series shipped meaningful features (multi-workspace Slack #195, /ops:credentials audit #184, ops-ci current-state filter #196, telegram preflight #185, userConfig schema upgrades #182). Per semver these should have been a minor bump. This release retroactively rolls them up into v2.1.0 with a single coherent CHANGELOG entry. No code changes — only: - plugin.json: 2.0.9 → 2.1.0 - CHANGELOG.md: new [2.1.0] entry consolidating Added/Fixed/Notes for the patch series - README header + What's-new section: refer to v2.1.0 - 11 docs/*.md badges + agents-reference subtitle + migration latest-stable note: v2.0.9 → v2.1.0 Marketplace pin (.claude-plugin/marketplace.json) bumped in follow-up PR. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>


Summary
preferences.jsongainsslack_workspaces[]— each entry hasname,token_env,kind. Skills iterate all entries and label output per workspace (Slack/lifecycle,Slack/stagery, etc.)slack_workspaces→ falls back to legacySLACK_MCP_ENABLED=truesingle-workspace path. Zero behaviour change for existing installs.bin/ops-slack-workspaceshelper: lists configured workspaces, resolves token env vars, smoke-tests each viaauth.test. Exits non-zero on any missing/invalid token.ops-unread,ops-go,ops-inbox,ops-comms,ops-dash,ops-statusall updated.Schema
Token env vars live in shell profile or Doppler — never committed.
Migration story
Existing single-workspace configs keep working unchanged. To opt into multi-workspace, run
/ops:setup slack— it appends toslack_workspaces[]without touching existing entries.MCP wrinkle
Claude Code's MCP is bound to one Slack token at startup. For the bound workspace,
mcp__claude_ai_Slack__*tools are used as before. For additional workspaces, skills fall back to direct curl with the workspace'stoken_envvalue. This is documented in each SKILL.md.Test plan
SLACK_MCP_ENABLEDunset →ops-unreadshowsavailable: false, skills skip with one-line noteSLACK_MCP_ENABLED=true→ legacy path, single unnamed workspace, unchanged behaviourops-slack-workspacesshows✓, ops-go labelsSlack/lifecycleops-slack-workspacesshows✗ token env not set, exits 1/ops:setup slack→ adds first workspace, prompts "Add another?", adds second, both persist in prefsTo add the Stagery workspace post-merge
🤖 Generated with Claude Code
Note
Medium Risk
Medium risk because it changes Slack availability/output schema in
bin/ops-unreadand channel detection inbin/ops-status, which downstream skills rely on; failures would primarily affect comms scanning rather than core data integrity.Overview
Adds multi-workspace Slack support via a new
preferences.jsonslack_workspaces[]schema (name + token env var + kind) while keeping the legacySLACK_MCP_ENABLED=truesingle-workspace path as a fallback.Updates
bin/ops-unreadto emit per-workspace Slack availability (resolving eachtoken_envsafely) and adjustsbin/ops-statusto treat Slack as configured if any workspace entry exists. Introducesbin/ops-slack-workspacesto list/test all configured workspace tokens (table or--json, non-zero exit on missing/invalid).Extends setup/skill docs to loop Slack setup for multiple workspaces, label results per workspace, and document MCP-bound vs direct-curl fallback behavior, and bumps the changelog to
2.0.9.Reviewed by Cursor Bugbot for commit 9f2f95d. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
Documentation
Improvements