A stateless incident management bot for Slack. No database. No web UI. Just Slack.
Fish Stick helps engineering teams manage incidents with minimal overhead. Create channels, log events, track timelines, and generate postmortem reports—all without setting up infrastructure.
The Problem: Most incident management tools are either too simple (basic Slack workflows) or too complex (enterprise platforms with a million knobs, dependencies, and steep learning curves).
The Solution: Fish Stick sits in the sweet spot:
- ✅ More powerful than Slack workflows (timeline generation, threading, analytics)
- ✅ Simpler than enterprise tools (stateless, no infrastructure)
- ✅ Fast to deploy (one command, no database)
- ✅ Easy to customize (clean TypeScript codebase)
- 🎲 Random channel names -
incident_furious_chicken,incident_brave_penguin - 📋 Timeline logging - Track events with timestamps
- 📢 Team updates - Threaded announcements to stakeholders
- 👨
✈️ IC tracking - Incident commander handoffs - 📊 Timeline reports - Auto-generated from channel history
- 📌 Pinned items - Key resources tracked automatically
- 🔒 Private incidents - Optional private channels
- 🧪 Test mode - Skip announcements during testing / game days
Requirements: Node.js 22+
Option A: Use the App Manifest (Recommended - 1 minute setup)
- Go to api.slack.com/apps
- Click Create New App → From an app manifest
- Select your workspace
- Copy and paste the contents of
slack-app-manifest.yml - Review and create the app
- For local development: Enable Socket Mode in app settings → Generate an App-Level Token with
connections:writescope - For production: Configure Request URL to
https://your-domain.com/slack/events - Install the app to your workspace
- Copy your tokens (Bot Token, Signing Secret, and App Token if using Socket Mode)
Option B: Manual Setup
- Go to api.slack.com/apps
- Create a new app (from scratch)
- Enable Socket Mode and generate an app token with
connections:writescope - Add these OAuth scopes (see full list below)
- Create the
/incidentslash command (see Slash Command Setup) - Install the app to your workspace
- Copy your tokens
- Create a public channel in your Slack workspace (e.g.,
#incidents) - Invite Fish Stick to the channel:
/invite @Fish Stick - Copy the channel ID:
- Right-click the channel name → View channel details
- Scroll down to find the Channel ID (looks like
C123456789) - You'll use this as
TEAM_UPDATE_CHANNEL_IDin your environment variables
This is where Fish Stick will post announcements whenever a new incident is created.
# Clone and install
git clone https://github.com/chrisdodds/fishstick.git
cd fishstick
npm install
# Configure environment variables
cp .env.example .env
# Edit .env with your tokens# Development (with auto-reload)
npm run dev
# Production (build first)
npm run build
npm startThat's it! The bot is now running in your Slack workspace.
| Command | Description |
|---|---|
/incident |
Create a new incident (opens modal) |
/incident update |
Send a threaded update to team channel |
/incident log <event> |
Log a timeline event |
/incident ic |
Check in as Incident Commander |
/incident timeline |
Generate incident report |
/incident resolve |
Mark incident as resolved |
/incident help |
Show available commands |
# Required
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secret
# Recommended
TEAM_UPDATE_CHANNEL_ID=C123456 # Where to post incident announcements
# Development (Socket Mode) - no public URL needed
SOCKET_MODE=true
SLACK_APP_TOKEN=xapp-your-app-token
# Production (HTTP Mode) - default, requires public URL
PORT=3000 # Server port (default: 3000)Radically simple:
- Incident metadata → Reconstructed from channel properties and pinned messages
- Timeline → Messages in the incident channel
- Summary → Pinned message in the incident channel
- No database, no web interface, no OAuth flow
The entire bot is stateless. You can restart it anytime without losing data—everything lives in Slack.
Deploy anywhere that runs Node.js:
# Build the image
docker build -t fishstick .
# Run with environment file
docker run --env-file .env fishstick
# Or pass environment variables directly
docker run \
-e SLACK_BOT_TOKEN=xoxb-... \
-e SLACK_SIGNING_SECRET=... \
-e SLACK_APP_TOKEN=xapp-... \
-e SOCKET_MODE=true \
-e TEAM_UPDATE_CHANNEL_ID=C123456 \
fishstick- Build command:
npm run build - Start command:
npm start - Set environment variables in dashboard
heroku create
heroku config:set SLACK_BOT_TOKEN=xoxb-...
heroku config:set SLACK_SIGNING_SECRET=...
heroku config:set SLACK_APP_TOKEN=xapp-...
heroku config:set SOCKET_MODE=true
git push heroku mainBot Token Scopes needed for your Slack app:
app_mentions:readchannels:historychannels:managechannels:readchat:writechat:write.publiccommandsgroups:historygroups:readgroups:writeim:historyim:readim:writeincoming-webhookmpim:historympim:readmpim:writepins:readpins:write
Fish Stick supports two deployment modes:
Best for: Local development, no public URL needed
- Enable Socket Mode in your Slack app settings
- Generate an App-Level Token with
connections:writescope - Set environment variables:
SOCKET_MODE=true SLACK_APP_TOKEN=xapp-your-token
- The
/incidentcommand Request URL can be left blank
Best for: Production deployments on any hosting platform
- Deploy your app to a publicly accessible URL
- In Slack app settings, set Request URL for:
- Slash Commands (
/incident):https://your-domain.com/slack/events - Interactivity & Shortcuts:
https://your-domain.com/slack/events - Event Subscriptions:
https://your-domain.com/slack/events
- Slash Commands (
- Set environment variables:
SOCKET_MODE=false # or leave unset PORT=3000 - Ensure Socket Mode is disabled in your Slack app settings
npm run dev # Dev mode with auto-restart (tsx watch)
npm run build # Build with esbuild
npm start # Production mode (run built code)
npm run lint # Run linter
npm test # Run tests
npm run test:watch # Run tests in watch mode
npm run test:coverage # Run tests with coverageProject Structure:
src/- TypeScript source codesrc/commands/- Slash command handlerssrc/listeners/- Slack event listenerssrc/parsers/- Message parsing logicsrc/utils/- Helper functionssrc/__tests__/- Jest unit testsbuild.js- esbuild configurationdist/- Compiled output (gitignored)
Contributions welcome! This is OSS.
Good First Issues:
- Add more tests
- Improve error messages
- Add integrations (PagerDuty, Jira, etc.)
- Better documentation
How to Contribute:
- Fork the repo
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT © 2025 Fishstick Labs
See LICENSE for details.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Security: Report vulnerabilities via GitHub Security tab
Built with ❤️ for incident responders everywhere.
