Skip to content

feat: Slack frontend (S1) — talk to the conductor from a Slack DM, approve from there - #469

Open
saucam wants to merge 3 commits into
mainfrom
feat/slack-s1
Open

saucam wants to merge 3 commits into
mainfrom
feat/slack-s1

Conversation

@saucam

@saucam saucam commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator

What

A Slack frontend for codeoid. You can message the conductor from a Slack DM and approve or deny tool calls there. It is the first Slack phase (S1: control).

  • Connection: Socket Mode (src/frontends/slack/socket.ts), so the daemon needs no public URL.
    • Envelopes are acked straight away and handled in a separate queue for each user.
    • Duplicates are dropped by envelope id and event id.
    • Slash commands answer within the ack window, or post to response_url when they take longer.
    • On disconnect it reconnects. Fatal auth errors stop it.
  • Frontend: src/frontends/slack/index.ts. It follows the Telegram template, including the GHSA-4g69 rules:
    • Only allowlisted Slack users can use it (CODEOID_SLACK_ALLOWED_USER_IDS), and each one acts with their own ZeroID key (/codeoid auth <key>). The bot refuses to start in local mode.
    • Token expiry is re-checked on every command.
    • A card can only be pressed by the user it was sent to, and only once.
    • Cards are dropped when that user's sign-in changes.
    • A card answered elsewhere (web, CLI) closes.
  • Cards show the call being approved (tool and input), its workstream, warnings, risk, whether it is sandboxed, and its cost.
  • Pasted secrets: a DM that looks like a key or token is refused and never sent on. This uses KNOWN_KEY_FORMATS, now exported from mcp/redact.ts with xapp- added.
  • Commands: /codeoid auth | ls | attach | conductor | stop | who. A plain DM goes to the conductor, which is created on first use.
  • Waiting sessions: SessionManager.onNeedsOwner tells you when another of your sessions is waiting on you, at most once every 10 minutes per session.
  • Approval ids: PendingToolApproval.messageId is added, so a card is tied to the exact message that asked.
  • Config: Settings manifest group for CODEOID_SLACK_*, a --no-slack flag, and docs/slack.md with the app manifest and setup steps.

Inbound text from other people (S2) and posting replies as you (S3) are later phases.

Audits

I did two rounds of deep audit, and the fixes are in this PR.

  • Round 1:
    • A card could be answered under a different sign-in.
    • Slack retries could cause duplicate prompts.
    • A pasted key could reach the transcript.
    • The token was exposed.
    • Plus some medium and low findings.
  • Round 2:
    • Cards did not show the call they asked about.
    • One user's slow handling blocked other users.
    • Attach truncated output without saying so.
    • Some key formats were not caught.
    • Plus some low findings.

Tests

  • New tests src/tests/slack-frontend.test.ts and src/tests/slack-socket.test.ts: 26 pass on Bun 1.3.13 and 1.4.4.
  • Typecheck is clean.
  • Full unit suite on Bun 1.3.13: 6201 pass. The 1 failure is the known bubblewrap failure ("conversations are the session's own"), which also fails on main.
  • Web tests: 39 fail, the same 39 that fail on main.
  • e2e: 23/23 pass.

🤖 Generated with Claude Code

saucam and others added 3 commits November 13, 2026 16:18
…prove from there

- Socket Mode client (no public URL, no new dependencies)
- /codeoid auth <key>: each allowlisted Slack user signs in with their own
  ZeroID key; a slash command isn't posted, so the key doesn't stay in Slack
- a DM goes to the conductor (made on first use with the user's own token)
  or an attached session; replies come back to the DM
- approval cards with Approve/Deny, naming the workstream, risk and sandbox;
  only the user a card was sent to can press it
- a DM when another of your sessions waits on you
  (SessionManager.onNeedsOwner)
- docs/slack.md: setup with an app manifest, and use

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it came to, fast acks without duplicate prompts, pasted keys refused

- a card is bound to the sign-in and session it came with; re-signing in
  drops it (the Telegram GHSA-4g69 rule)
- events and presses are acknowledged at once and handled in order; a
  retried event is dropped; a slow slash command answers at response_url;
  a refreshed connection replaces the old one only once it is up
- a key or token pasted into the DM is never sent on
- codeoid's Slack variables are CODEOID_SLACK_* (kept from every agent and
  server config like the rest of CODEOID_); settings manifest entries
- attaching shows what the session already waits on; a card answered
  elsewhere closes (PendingToolApproval.messageId)
- model text escaped, no unfurling; the DM opened at sign-in (im:write);
  question tools point to the web UI; cut text says so; optional
  CODEOID_SLACK_TEAM_ID; notices rate-limited; API calls time out

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e user's handling never blocks another's

- cards show the command, the file and what it writes, or the plan, from the
  call's input (the description only names the arguments), split across
  sections and cut only with a note; the same on the attach path
- one inbound queue per Slack user, each envelope bounded at 60 s
- resent presses and commands dropped like events; a repeated press says
  nothing more; every live socket closed on stop; a start that can't reach
  Slack retries, a refused app token stops
- the pasted-key check reuses the known key formats (redact.ts
  KNOWN_KEY_FORMATS) plus JWTs and private keys
- a card answered before it was posted isn't posted; cards close for every
  Slack user holding them; their bookkeeping is let go with them
- a DM re-checks the sign-in after each wait; a DM that can't be opened at
  sign-in is said

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@saucam
saucam requested a review from rsharath November 13, 2026 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant