Control local Claude Code, Codex CLI, and Pi sessions from Slack. Each native session gets a private Slack channel where prompts, responses, progress, and attachments flow both ways.
Version 2 has one command language everywhere: sab in a shell and /sab-*
in Slack. The active Slack channel selects its provider; only creation and
provider switching need an explicit claude, codex, or pi target.
Provider processes live in detached-capable tmux sessions. Ghostty is an optional viewport, not a process-lifetime requirement: close every terminal and the agents continue running; open or focus one later without resuming or duplicating the native conversation.
Warning
This is remote code execution by design. Slack-spawned Claude sessions
default to --dangerously-skip-permissions; Slack-spawned Codex sessions
default to --dangerously-bypass-approvals-and-sandbox (--yolo). Pi's
built-in tools are unrestricted by default; SAB's optional --safe flag adds
fail-closed Slack approval per tool call. Anyone able to act as the bridge
owner can steer processes with that Mac user's privileges. Read
SECURITY.md before installing.
Note
The daemon currently targets macOS and launchd. Ghostty is needed only when terminal viewports are wanted. Claude uses its Channels API; Codex uses hooks, tmux, and a loopback App Server event proxy; Pi uses an explicitly loaded native extension.
Note
Multi-machine support is being built around one Slack-facing coordinator and
enrolled execution nodes, without duplicating the app or /sab-* commands.
The current foundation can securely enroll and authenticate a node when an
operator explicitly enables its separate WSS listener, but it does not yet
route provider sessions to that node. Existing installs open no new listener
and remain local-only. See
the accepted multi-node architecture.
| Capability | Claude Code | Codex CLI | Pi |
|---|---|---|---|
| Private channel per native session | ✓ | ✓ | ✓ |
| Slack prompts and attachments | ✓ | ✓ | ✓ |
| Return generated files to Slack | ✓ | ✓ | ✓ |
| Final responses and live working status | ✓ | ✓ | ✓ |
| Selected interim progress | ✓ | ✓ | managed runs |
| Model and effort controls | ✓ | ✓ | ✓ |
| Remote permission decisions | ✓ | ✓ | --safe |
| Token and cost usage | ccusage |
ccusage |
native ledger |
| Provider handoff in one channel | ✓ | ✓ | ✓ |
| Cross-session team delegation | ✓ | ✓ | ✓ |
| Persistent plans, goals, and review | — | — | adaptive /sab-run |
| Claude subscription switching | ✓ | — | — |
| Chrome integration | --chrome |
no counterpart | no counterpart |
While a turn runs, its status and elapsed timer remain the newest channel item.
Daemon restarts re-adopt active turns and their original duration. Codex's
loopback event proxy mirrors completed semantic commentary and uses a completed
App Server turn as an exact final-answer fallback when Codex omits its Stop
hook. It excludes commands, output, diffs, plans, reasoning, and deltas; Stop
and App Server completion share a durable turn-level deduplication claim.
For a newly launched automation only, the typed root thread/started event also
supplies the native identity when that Codex release defers SessionStart; SAB
still requires the exact pending automation cwd, tmux, and provider-process
ancestry before adopting it. Child threads and ordinary sessions are ignored.
The Codex runner keeps the correlated App Server alive through the proxy's
bounded shutdown drain so a closing TUI cannot invalidate the final's ancestry
proof before delivery.
If Codex rejects a submitted turn because its selected model is at capacity,
SAB replaces the working timer with that actionable failure instead of leaving
the channel apparently busy. The detector requires the exact current TUI
warning on a stable idle input surface and ignores stale terminal scrollback.
If Codex starts a fallback model during a turn, SAB shows the actual model and
posts a warning while retaining the requested model/effort for the next
restart. A native Codex change becomes the new durable selection only when the
idle TUI renders its explicit Model changed to … confirmation; a plain footer
mismatch is treated as a possible capacity fallback and never changes the next
resume. Claude and Pi likewise resume with their latest known native
model/effort rather than their original launch values.
If Codex omits UserPromptSubmit, SAB starts tracking a bridge-injected turn at
the tmux boundary. If it also omits Stop, the correlated App Server
turn/completed final completes the exact owner or delegated turn. When neither
stable completion source arrives, two unchanged idle observations after the
grace period clear only the exact provider/task fences; a delegated task is
failed and released without replay. Replacement sessions and historical failed
tasks are never mutated. Status edits share a workspace-wide rate-safe, coalescing queue so a
long-running timer cannot starve ordinary Slack responses.
Claude AskUserQuestion forms use their structured hook payload, so Slack keeps
the question header, prompt, recommendation, option descriptions, and previews
separate from concise answer buttons. A bounded terminal parser remains only as
a restart/legacy fallback. Claude's explicitly configured local MCP server is
selected through the approved --channels path, so detached starts and resumes
do not depend on an interactive development-channel confirmation. When Claude
asks for workspace trust, SAB explicitly selects the affirmative row for the
owner-requested directory instead of assuming Enter is safe.
The bridge never parses Codex transcript JSONL. See
ARCHITECTURE.md, the provider feasibility notes under
docs/, and Managed Pi runs.
- macOS
- Node.js 20 or later,
tmux,jq, andgit - Optional Ghostty for terminal viewports
- At least one configured Claude Code, Codex, or Pi CLI
- A Slack workspace where you may create or update an app
brew install node tmux jq gitChoose the provider set. A flagless install remains Claude-only for upgrades from older releases.
# Claude only
curl -fsSL https://raw.githubusercontent.com/SergioTCG/SlackAgentBridge/main/install.sh | bash
# One provider
curl -fsSL https://raw.githubusercontent.com/SergioTCG/SlackAgentBridge/main/install.sh | bash -s -- --provider codex
curl -fsSL https://raw.githubusercontent.com/SergioTCG/SlackAgentBridge/main/install.sh | bash -s -- --provider pi
# Claude + Codex, or all three
curl -fsSL https://raw.githubusercontent.com/SergioTCG/SlackAgentBridge/main/install.sh | bash -s -- --provider both
curl -fsSL https://raw.githubusercontent.com/SergioTCG/SlackAgentBridge/main/install.sh | bash -s -- --provider allThe installer opens a pre-filled Slack app page. Create the app, install it,
then supply the bot token (xoxb-…) and a Socket Mode app token (xapp-…,
connections:write). Run /sab-claim to bind the bridge to its owner.
The sole canonical manifest is
slack/app-manifest.json. A 1.x → 2.0 upgrade must
apply this manifest to the existing Slack app and reinstall that app once so
Slack registers /sab-* and removes the old provider-prefixed commands. This
does not require a second app, new tokens, or new OAuth scopes.
After upgrading to the session-team release, apply and reinstall the same
canonical manifest once more so Slack registers /sab-team. Existing tokens
and OAuth scopes remain valid.
The interactive-management release also enables the app's Home tab and routes
app_home_opened through the same Socket Mode daemon. Apply the canonical
manifest to the existing app and reinstall it once. No new OAuth scope,
token, callback URL, app, or daemon is required.
Fresh installs use ~/.slack-agent-bridge. Existing
~/.claudeslackproxy checkouts, ~/.config/ccs state, session channels, and the
historical si.sergej.claudeslackproxy LaunchAgent are retained. The installer
removes old launcher symlinks and installs only sab on PATH.
The staged install-codex.sh and install-pi.sh helpers can add provider
support without restarting the live daemon. Activation still belongs in a
controlled maintenance window. If a newly staged Pi extension meets an older
daemon, model and effort changes fail closed with a restart-required message;
they do not mutate an unverified native session. A staged activation must be
run from the checkout already named by the live LaunchAgent; invoking it from
an isolated development worktree fails before changing hooks, configuration,
Git state, or the public sab link.
Existing 2.0 installations should read Migrating to 2.1. Version 2.1 adds interactive management, App Home, durable team scheduling, and the lifecycle/delivery hardening needed for long-running coordinator and worker sessions. It does not bulk-migrate session state or change the historical LaunchAgent, local port, configuration path, or Slack token set.
Start a provider session in the current directory:
sab new claude --model opus --effort max --dsp --chrome
sab new codex --model gpt-5.6-sol --config 'model_reasoning_effort="xhigh"' --yolo
sab new pi --model qwen38-local/qwen3.8-27b --thinking xhighUse another working directory with --cwd DIR. All later arguments are passed
to the selected provider after SAB's provider-specific validation where the
daemon is involved.
Manage optional terminal viewports without changing session lifetime:
sab terminal list
sab terminal list --json
sab terminal open 01a0145c
sab terminal close 01a0145c
sab terminal open here
sab terminal close here
sab terminal open-all # show-all is an alias
sab terminal close-allopen focuses an already attached Ghostty window. close detaches that exact
tmux client; it never kills the tmux session or provider. Bulk actions operate
only on authoritative active sessions, not standby or provisional provider
legs.
Other script-safe subcommands are:
sab account list
sab account add work
sab upload --grant TOKEN -- FILE_PATH...
sab team context --json
sab team send --to WORKER_ALIAS --stdin
sab team inbox --active --limit 20 --page --json
sab team checkpoint --task TASK_ID --pending ci,review --stdin
sab team complete --task TASK_ID --generation N --stdin
sab team message --task TASK_ID --stdin
sab team release --task TASK_ID
sab team continue --task TASK_ID --stdin
sab team cancel --task TASK_ID
sab team mutation --request-id REQUEST_ID --task TASK_ID
sab team mode draining
sab team wait --task TASK_ID --json
sab automation create ...
sab automation status EXTERNAL_KEY
sab automation stop EXTERNAL_KEY --archive
sab node status
sab node listsab node invite, enroll, and revoke currently support authenticated
transport validation only. They do not make remote provider sessions available.
The coordinator listener is off unless SAB_NODE_LISTEN is explicitly set; a
non-loopback bind also requires SAB_NODE_PUBLIC_URL, SAB_NODE_TLS_KEY, and
SAB_NODE_TLS_CERT. See the multi-node architecture document before enabling
this preview surface.
There are no public ccs*, sab-cc, sab-codex, sab-pi, sab-upload, or
sab-automation executables in 2.0.
A session channel always acts on its authoritative provider.
| Command | Effect |
|---|---|
/sab-new <claude|codex|pi> [folder] [flags] |
Choose a provider/project interactively, or start a headless session directly |
/sab-model [model] |
Choose or change this session's model |
/sab-effort [level] |
Choose or change reasoning/thinking effort |
/sab-flags [flags] |
Show or replace allowlisted launch flags |
/sab-update [current|all] |
Choose an update interactively, or update this/all eligible sessions directly |
/sab-stop |
Interrupt the current turn without ending the session |
/sab-switch <claude|codex|pi> [new] |
Hand this channel to another native provider leg |
/sab-kill [here|session-id] |
End one exact provider process and keep its channel resumable |
/sab-status [claude|codex|pi] |
Show this session plus controls, or filter the control-channel list |
/sab-usage [provider] [days [n]|models|limits] |
Show provider usage |
/sab-run … |
Control Pi adaptive routing and managed runs |
/sab-account [name|default] |
Show or change a Claude subscription |
/sab-terminal [list|open|close|open-all|close-all] |
Manage optional viewports |
/sab-team [create|add|status|auto|manual|drain|resume|permissions|remove|close] |
Link SAB sessions for auditable delegation, bounded continuation, and queue control |
/sab-health |
Show daemon health |
/sab-cleanup |
Archive dormant session channels |
/sab-claim |
Claim an unowned bridge |
/sab-help |
Show the command list |
Ordinary messages are injected into the active native session. Attachments are
downloaded under the bridge attachment directory and their local paths are
included in the prompt. Dormant owner sessions resume headlessly; opening a
terminal is never required. A Claude wake is successful only after its exact
SessionStart claim; a provider that exits after briefly creating tmux is
retried once and then reported visibly while the queued message remains safe.
Management commands are interactive when invoked without arguments. /sab-model
and /sab-effort show provider-valid selectors; /sab-terminal, /sab-update,
/sab-switch, /sab-new, and /sab-team show bounded buttons or pickers.
/sab-status adds a consolidated dashboard for the current session, while the
control-channel dashboard exposes bridge-wide session, terminal, update,
health, and usage controls. Parameterized forms such as /sab-terminal open,
/sab-model gpt-5.6-sol, and /sab-update all remain available. Use
/sab-update current for a non-interactive current-session update.
Every click is rechecked against the immutable channel ID, exact authoritative
session/provider, current provider catalog, transition state, and the existing
team/update safety gates. A stale control therefore fails visibly instead of
acting on a replacement leg. Session identity is held unchanged across slow
lookups, Claude's standard and 1M-context model entries carry distinct exact
provider IDs, and team buttons are tied to the exact team that rendered them.
Broad update and team-close actions require Slack
confirmation. These panels are only a presentation layer over the normal
/sab-* dispatcher.
Pi model and effort controls additionally require the running extension to
advertise exact-session fencing. A Pi process preserved across a daemon upgrade
may still contain the older extension; SAB refuses the mutation and asks for
/sab-update current instead of assuming that process understands the new
control protocol.
Open Slack Agent Bridge under Slack's Apps section for a persistent owner dashboard. It lists authoritative session channels and provides exact session, terminal, model, effort, switch, update, team, usage, health, and new-session controls. The new-session modal requires an explicit provider and a current top-level project folder; launch flags pass through the existing provider allowlist.
App Home rebuilds from authoritative state whenever it is opened or refreshed. Results remain visible in the bridge control channel or affected session channel rather than becoming private, unaudited Home-only state. Non-owners see a restricted view containing no session IDs, channel IDs, folders, settings, or actions.
/sab-update all is the quiet-period maintenance sweep. It considers only the
authoritative live session bound to each channel, skips any session with an
active turn, question, permission, provider switch, managed Pi run, automation
ownership, delegated worker task, or restart already in progress, and reports
every skip or failure.
Each represented provider CLI is updated once; every eligible native session is
then resumed with its existing cwd, identity, account, model, effort, and launch
flags. Messages arriving during the relaunch are queued for that same session.
If the provider replaces its native identity during maintenance, the queue and
restart fences follow only that verified in-place rebind. Direct input reopens
only after the shared ordered drain submits all queued prompts, including later
arrivals. Launch arguments and reconnecting provider streams never consume that
queue independently. A failed wake or startup-metadata call preserves the
queue, reports the exact recovery action, and allows a later owner message to
retry a genuinely dormant session. One-use artifact grants embedded in those
prompts follow only the verified same-provider/channel native replacement.
An idle Codex resume may not emit SessionStart; after a bounded hook grace
period, every update, settings change, and ordinary Slack wake recovers it only
by finding the Codex process beneath the exact replacement tmux and validating
that ancestry before repairing the PID/channel binding. Daemon restart applies
the same check to an interrupted hookless resume, so /sab-terminal open
becomes available again without a second Codex process or a synthetic prompt.
SAB-managed Codex TUIs disable Codex's interactive startup update check. This
prevents a detached new, resumed, automation, or provider-switch session from
waiting on a local update chooser before it can bind to Slack. Codex upgrades
remain explicit through /sab-update and /sab-update all; the internal
setting is not added to stored or user-visible launch flags.
Flagless /sab-new claude and /sab-new codex use the dangerous defaults
described above. Explicit flags replace those defaults. Operator overrides
remain available through the existing CCS_NEW_FLAGS, CCS_RESUME_FLAGS,
CCS_CODEX_NEW_FLAGS, CCS_CODEX_RESUME_FLAGS, CCS_PI_NEW_FLAGS, and
CCS_PI_RESUME_FLAGS settings. Pi's --safe controls tool approval; Pi's
native --approve separately controls project-resource trust.
Run /sab-switch <target> from an idle session channel. The bridge captures a
private structured handoff, starts or resumes the target's own native
conversation, validates it privately, and changes the channel mapping only
after success. The source native leg is then dormant standby state; its terminal
and provider process have stopped, but its resumable native ID and settings are
preserved for a round trip.
Messages arriving during the transaction are queued. Failure or daemon restart
rolls back to the source. Provider-specific model, effort, flags, and Claude
account settings are never translated. Instruction reconciliation reads only
repository-root AGENTS.md and CLAUDE.md, proposes an ordinary reviewed Git
patch, and never imports global memory or MEMORY.md.
/sab-status in a session channel shows the collaborator picker. The bridge
invites a selected user to the private channel first and adds them to the prompt
allowlist only after invitation succeeds. Collaborators may send labelled
prompts to a live allowed session; they cannot run commands, answer permission
requests, or resurrect it.
One SAB session channel can coordinate explicitly linked worker channels without giving an agent Slack credentials or arbitrary channel access:
/sab-team create hexagonal-cleanup
/sab-team add
/sab-team permissions codex-barrique-parallel-1 files on
/sab-team status
/sab-team auto # opt into bounded continuation; use /sab-team manual to disable
/sab-team drain # finish active tasks but dispatch nothing queued; resume later
The owner chooses workers with Slack's private-channel picker. Team identity is bound to immutable channel IDs and survives channel renames and provider switching. The default topology permits coordinator → worker tasks and worker → coordinator replies/results; worker-to-worker relay is disabled. File relay is off per worker until explicitly enabled. Automatic mode coalesces concurrent executor events into one coordinator wake because that wake rereads the complete authenticated inbox. If a long-lived coordinator consumes its 20-dispatch turn budget, a pending authenticated worker event renews one bounded continuation budget; it does not grant unlimited dispatch. If a resumed Codex coordinator omits its completion hook, SAB uses bounded exact-process idle confirmation to release only the stale turn fence; prolonged legitimate waits are reported once in the coordinator channel.
Drain mode is separate from continuation mode. /sab-team drain lets active
workers finish while preventing every queued claim and automatic coordinator
wake; /sab-team resume makes queued work eligible again. It does not cancel
or stop a provider. From an authenticated coordinator turn, sab team cancel
and sab team replace control one exact queued task, while sab team message
adds an audited instruction or answer to the exact worker/session currently
owning an active task.
New tasks use two-phase completion. A provider final or a continuously proved
hookless idle surface creates an awaiting_release turn report and keeps the
worker reserved. Workers publish the complete list of unfinished gates with
sab team checkpoint, clear that list and declare readiness with sab team complete, and only then may the coordinator use sab team release. A
coordinator follow-up invalidates the previous readiness declaration. If work
was already released accidentally, sab team continue creates a new, linked,
auditable task rather than reopening a terminal journal entry. An accepted
follow-up immediately fences completion and release until exact provider
delivery succeeds; reports are bound to the corresponding durable work
generation, so a delayed final from the preceding turn cannot release newer
work.
Eligible owner turns receive private, provider-neutral role/tool context. A
delegated worker receives an exact task header, while collaborators receive no
lateral authority. The JSON-safe sab team CLI supports peers, send, bounded
filtered/paginated inbox, wait, reply/checkpoint, explicit complete/release,
linked continuation, mutation receipts, queued-task cancel/replace, active-task
messaging, drain/resume, and task-bound file transfer. Tasks are atomically
journaled, visibly posted in both channels, queued only for a safe idle worker,
correlated with provider-stable turn reports, and fenced against restart/stale
leg duplication. Dormant peers are never resurrected by another agent.
If an exact live Codex worker visibly returns to its idle prompt after a task
but omits the completion hook, SAB records a warning-bearing turn report; it
does not fabricate a failure, invent a final, or release the worker. An already
idle pre-upgrade task found during daemon boot has no equivalent continuous
proof and still fails closed. An awaiting_release task and its exact worker
binding survive daemon restart, including while the provider is dormant.
An owner message in the dormant worker channel may wake that same reserved
session without becoming unrelated provider input; any durable coordinator
follow-up is already visible in both channels and is delivered after re-adoption.
Availability and the queued → dispatching task claim are persisted together,
and context output includes observation time plus the last task/availability
transition reason.
See Session teams for the complete workflow, limits, recovery behavior, and file boundary. Initial relay is local-node only; the durable identities are compatible with the accepted multi-node protocol.
Owner prompts are adaptively routed by default. Use /sab-run mode auto,
always, or native; /sab-run direct <prompt> bypasses routing once.
/sab-run [plan] <goal> [--minutes=N --turns=N --agents=N --reviews=N] forces a
bounded planner/worker/reviewer run. Status and control actions are
/sab-run status, approve, pause, continue, and cancel.
Use the JSON-safe client instead of constructing curl payloads:
sab automation create \
--external-key 'github:org/repo#123' \
--cwd /Users/example/Code/repo-worktree \
--provider claude \
--collaborator U0123456789 \
--prompt-file /path/to/prompt.txt \
-- --model opus --effort max --dsp --chrome
sab automation status 'github:org/repo#123'
sab automation stop 'github:org/repo#123' --archive
sab automation validate-flags --provider codex -- \
--model gpt-5.6-sol --effort xhigh --yoloThe loopback-only API at 127.0.0.1:8877 provides
POST /automation/sessions, GET /automation/sessions/:externalKey, and
POST /automation/sessions/:externalKey/stop. externalKey is durable and
idempotent. Creation journals before launch, correlates the exact tmux/native
session/channel, invites and resolves every collaborator before whitelisting,
and injects the initial prompt at most once without an artifact grant. Exact
stop never delegates to bulk cleanup and archives only the correlated channel.
validate-flags is local and side-effect free. It lets project automation
validate and canonicalize the same provider argv before allocating its own
worktrees, databases, or ports. Codex accepts both split and inline model/effort
forms; arbitrary --config remains forbidden, while safe effort input is
translated to Codex's native model_reasoning_effort override.
Ask naturally for a file in a session channel. An accepted prompt receives a
short-lived, single-use capability for sab upload; the destination remains
fixed by the daemon. Paths must resolve to regular files inside that session's
workspace. At most ten files and 100 MiB total may be delivered. A grant cannot
be replayed or redirected.
sab account add work
sab account listUse /sab-account work in a Claude channel or pass --account work to
/sab-new claude. Tokens remain in ~/.config/ccs/accounts with mode 0600
and never enter process arguments.
Version 2 intentionally removes the provider-prefixed Slack namespaces and all legacy terminal launchers. It preserves the data plane needed to resume existing work:
~/.config/ccs, old records with no provider, and historical tmux names;- existing private channels and immutable channel mappings;
- existing
~/.claudeslackproxyinstallations; - port
8877,CCS_*operator settings, and the historical LaunchAgent label; - the existing Slack app and token set.
Read Migrating to 2.0 before rollout. Apply the
canonical manifest to the existing Slack app, install the release during a
maintenance window, and run the canary in
docs/release-checklist.md. Do not run two daemons
with the same Socket Mode token.
- Config/state:
~/.config/ccs/ - Logs:
~/.config/ccs/daemon.log - Disable self-update:
CCS_AUTO_UPDATE=0 - Optional dockless Ghostty viewports:
CCS_GHOSTTY_HIDDEN=1 - Local API: loopback port
8877; never proxy or expose it
Required validation is defined in AGENTS.md. Releases use the
stability policy and complete
release checklist. Live Slack,
Ghostty, Claude, Codex, and Pi tests belong in a controlled maintenance window
or on a separate Slack app and token set.
MIT. Slack Agent Bridge is not affiliated with Anthropic, OpenAI, Slack, or the Pi project.