name: setup-spa description: "SPA (Spawn's Personal Agent) — Slack bot that pipes threads into Claude Code sessions" disable-model-invocation: true
SPA — Spawn's Personal Agent
Slack bot that listens in #proj-spawn via Socket Mode. When @mentioned, it collects the full thread, pipes it into a claude -p session, and streams Claude Code's responses back to the Slack thread in real-time.
Subsequent thread replies in tracked threads auto-trigger new Claude Code runs.
Slack App Setup
Option A: Create from manifest (recommended)
- Go to https://api.slack.com/apps
- Click Create New App > From a manifest
- Select your workspace
- Paste the contents of
slack-manifest.ymlfrom this directory - Click Create
- Socket Mode: Settings > Basic Information > scroll to "App-Level Tokens" > Generate Token and Scopes > add
connections:writescope > copy thexapp-...token - Install to Workspace: Features > OAuth & Permissions > Install > copy the
xoxb-...Bot User OAuth Token - Invite the bot to the target channel:
/invite @spa - Get the channel ID: Right-click channel name > View channel details > copy the ID (starts with
C)
Option B: Manual setup
- Go to https://api.slack.com/apps > Create New App > From scratch
- Name it
SPA, select the workspace - Socket Mode: Settings > Socket Mode > Enable > generate app-level token with
connections:writescope > savexapp-... - Event Subscriptions: Features > Event Subscriptions > Enable > subscribe to bot events:
app_mention,message.channels,message.groups - OAuth Scopes: Features > OAuth & Permissions > Bot Token Scopes:
app_mentions:read,channels:history,channels:read,groups:history,groups:read,chat:write,reactions:write - Install to Workspace > save
xoxb-...token - Invite bot to channel, get channel ID
Env Vars
| Var | Description |
|---|---|
SLACK_BOT_TOKEN |
Bot User OAuth Token (xoxb-...) |
SLACK_APP_TOKEN |
App-Level Token for Socket Mode (xapp-...) |
SLACK_CHANNEL_ID |
Channel ID to listen in (e.g. C0123456789) |
GITHUB_REPO |
Target repo context (default: OpenRouterLabs/spawn) |
REPO_ROOT |
Working directory for Claude Code (default: cwd) |
GitHub auth uses the gh CLI — run gh auth login before starting.
start-spa.sh Template
Create .claude/skills/setup-spa/start-spa.sh (gitignored):
#!/bin/bash
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export SLACK_BOT_TOKEN="xoxb-YOUR-BOT-TOKEN"
export SLACK_APP_TOKEN="xapp-YOUR-APP-TOKEN"
export SLACK_CHANNEL_ID="C0000000000"
export GITHUB_REPO="OpenRouterLabs/spawn"
export REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
exec bun run "${SCRIPT_DIR}/main.ts"
Install
bun install # from repo root — workspace install
Systemd Service
Create /etc/systemd/system/spawn-spa.service:
[Unit]
Description=SPA — Spawn's Personal Agent
After=network.target
[Service]
Type=simple
User=lab
Group=lab
WorkingDirectory=/home/lab/spawn/.claude/skills/setup-spa
ExecStart=/bin/bash /home/lab/spawn/.claude/skills/setup-spa/start-spa.sh
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment="PATH=/home/lab/.local/bin:/home/lab/.bun/bin:/usr/local/bin:/usr/bin:/bin"
[Install]
WantedBy=multi-user.target
Then:
sudo systemctl daemon-reload
sudo systemctl enable --now spawn-spa
Verify
sudo systemctl status spawn-spa
journalctl -u spawn-spa -f
# Test: @mention spa in channel → Claude Code runs, response streams back
# Test: Reply and @mention again → resumes same session
State
Thread-to-session mappings are persisted at ~/.config/spawn/slack-issues.json.
Troubleshooting
| Symptom | Fix |
|---|---|
ERROR: SLACK_BOT_TOKEN env var is required |
Set all required env vars in start-spa.sh |
| Bot doesn't respond to @mentions | Verify bot is invited to the channel; check SLACK_CHANNEL_ID |
| Claude errors with permission denied | Ensure --dangerously-skip-permissions is working, or run in a sandbox |
| Responses truncated | Slack has ~4000 char limit per message; long responses show the tail |
| Hourglass reaction | A Claude run is already active for that thread; wait for it to finish |