# Setup Spa

> SPA (Spawn's Personal Agent) — Slack bot that pipes threads into Claude Code sessions

- Skill: `openrouterlabs/setup-spa` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add openrouterlabs/setup-spa`
- Raw SKILL.md: https://api.skillmd.com/api/skills/openrouterlabs/setup-spa/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: OpenRouterLabs (https://skillmd.com/u/openrouterlabs)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/openrouterlabs/setup-spa

---


# 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)

1. Go to https://api.slack.com/apps
2. Click **Create New App** > **From a manifest**
3. Select your workspace
4. Paste the contents of `slack-manifest.yml` from this directory
5. Click **Create**
6. **Socket Mode**: Settings > Basic Information > scroll to "App-Level Tokens" > **Generate Token and Scopes** > add `connections:write` scope > copy the `xapp-...` token
7. **Install to Workspace**: Features > OAuth & Permissions > Install > copy the `xoxb-...` Bot User OAuth Token
8. **Invite the bot** to the target channel: `/invite @spa`
9. **Get the channel ID**: Right-click channel name > View channel details > copy the ID (starts with `C`)

### Option B: Manual setup

1. Go to https://api.slack.com/apps > **Create New App** > **From scratch**
2. Name it `SPA`, select the workspace
3. **Socket Mode**: Settings > Socket Mode > Enable > generate app-level token with `connections:write` scope > save `xapp-...`
4. **Event Subscriptions**: Features > Event Subscriptions > Enable > subscribe to bot events: `app_mention`, `message.channels`, `message.groups`
5. **OAuth Scopes**: Features > OAuth & Permissions > Bot Token Scopes: `app_mentions:read`, `channels:history`, `channels:read`, `groups:history`, `groups:read`, `chat:write`, `reactions:write`
6. **Install to Workspace** > save `xoxb-...` token
7. **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):

```bash
#!/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

```bash
bun install    # from repo root — workspace install
```

## Systemd Service

Create `/etc/systemd/system/spawn-spa.service`:

```ini
[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:

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now spawn-spa
```

## Verify

```bash
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 |

