Autonomous AI issue fixer — a Discord bot that uses the Claude Code CLI to fix GitHub issues, run your CI checks, and open pull requests. No middleware, no containers, just claude -p in a git worktree streaming results to Discord.
Existing tools for autonomous issue fixing are clunky and don't work well with Claude Max subscriptions. fixbot is a lightweight alternative that calls the Claude Code CLI directly. You type fix 42 in Discord, and fixbot handles the rest — fetching the issue, implementing the fix, running your quality gates, and opening a PR for review.
Discord: "fix 42"
├─ Fetch issue #42 from GitHub
├─ Create isolated git worktree from target branch
├─ Run claude -p with issue details + quality gates
│ ├─ Claude implements the fix
│ ├─ Runs your CI checks (lint, typecheck, tests, etc.)
│ ├─ Commits and pushes an ai/* branch
│ └─ Opens a pull request via gh CLI
├─ Stream progress updates to Discord
└─ Post result with PR links
├─ React ✅ to merge
└─ React ❌ to reject
Sessions persist across restarts. Reply to any bot message to continue the conversation with the same Claude session.
Before installing fixbot, make sure you have the following set up on the machine where it will run:
| Dependency | Minimum version | How to verify |
|---|---|---|
| Node.js | 20+ | node --version |
| Claude Code CLI | Latest | claude --version |
| GitHub CLI | 2.0+ | gh --version |
| A Discord bot | — | Setup guide |
Claude Code must be authenticated (via Claude Max or API key), and the GitHub CLI must be authenticated with access to your target repo. See the detailed setup guides:
- Setting up Claude Code — install, authenticate, configure model
- Setting up a Discord bot — create application, enable intents, invite to server
git clone https://github.com/cj-vana/fixbot.git
cd fixbotnpm installcp .env.example .envOpen .env and fill in the required values:
# Required
DISCORD_BOT_TOKEN=your-discord-bot-token
DISCORD_CHANNEL_ID=your-discord-channel-id
GITHUB_TOKEN=your-github-pat-with-repo-scope
TARGET_REPO=owner/repoSee the full configuration reference below for all available options.
node index.mjsYou should see:
fixbot starting...
fixbot online as fixbot#1234
fixbot ready
All configuration is via environment variables in .env.
| Variable | Description |
|---|---|
DISCORD_BOT_TOKEN |
Discord bot token (how to get one) |
DISCORD_CHANNEL_ID |
Channel ID where fixbot listens (how to find it) |
GITHUB_TOKEN |
GitHub personal access token with repo scope |
TARGET_REPO |
GitHub repo to fix issues in and create PRs against (owner/repo) |
| Variable | Default | Description |
|---|---|---|
ISSUES_REPO |
same as TARGET_REPO |
Repo to fetch issues from, if different from target |
TARGET_BRANCHES |
main |
Comma-separated branches to target for PRs |
QUALITY_GATES |
npm test |
Comma-separated commands Claude runs before committing |
CLAUDE_MODEL |
opus |
Claude model to use (opus, sonnet, haiku) |
MAX_CONCURRENT_JOBS |
1 |
Max parallel fix jobs |
JOB_TIMEOUT_MS |
1800000 |
Job timeout in ms (default 30 min) |
WORKSPACE_PATH |
./workspace |
Where to store repo clone and worktrees |
STATE_PATH |
./data/sessions.json |
Where to persist session state |
Send these messages in the configured Discord channel:
| Command | What it does |
|---|---|
fix <number> |
Fix issue #N, creating PRs for all target branches |
fix <number> <branch> |
Fix issue #N, targeting only the specified branch |
| Reply to bot message | Continue the Claude session for that issue |
| React ✅ on result | Merge all PRs for that issue |
| React ❌ on result | Close all PRs for that issue |
fix 42 # Fix issue #42, PR against all target branches
fix 42 develop # Fix issue #42, PR against develop only
Replying to a bot result message lets you give Claude follow-up instructions in the same session — useful for requesting changes before merging.
Set QUALITY_GATES to match your project's CI:
# JavaScript / TypeScript
QUALITY_GATES=npm run lint,npm run type-check,npm run build,npm test
# Python
QUALITY_GATES=ruff check .,mypy .,pytest
# Go
QUALITY_GATES=go vet ./...,go test ./...Claude runs each gate in order, fixes failures, and re-runs until they pass (or gives up and reports the error).
If your repo uses staging and production branches:
TARGET_BRANCHES=develop,mainRunning fix 42 creates separate PRs targeting each branch. Running fix 42 develop targets only that branch.
If your issues live in a different repo from your code:
TARGET_REPO=myorg/myapp
ISSUES_REPO=myorg/myapp-issuesindex.mjs Entry point — Discord client, message routing
lib/
config.mjs Environment variable loading and validation
claude.mjs Spawns claude -p, streams NDJSON events
fix.mjs Fix command handler — job queue, orchestration
follow-up.mjs Reply handler — continues existing Claude sessions
reactions.mjs Reaction handler — merge/reject PRs via reactions
github.mjs GitHub API client (issues, PRs, checks)
discord-util.mjs Message chunking and formatting
sessions.mjs Persistent session state (JSON file)
workspace.mjs Git workspace management (clone, worktrees, cleanup)
Discord message "fix 42"
→ handleFix() queues the job
→ runFixJob() fetches issue, creates worktree from target branch
→ runClaude() spawns claude -p with system prompt + issue body
→ Claude implements fix, runs quality gates, pushes ai/* branch, creates PR
→ Bot posts result to Discord with PR links and changed file list
→ Session saved for follow-up replies and reactions
Claude is constrained to ai/* branches only. It cannot push to, merge into, or checkout any protected branch. The system prompt enforces these rules:
- Only
ai/*branches are pushed - PRs are created but never merged by Claude — that requires a human reaction in Discord
- No force pushes, hard resets, or destructive git commands
- Protected branches (configured via
TARGET_BRANCHES) are never modified directly
Sessions are stored in data/sessions.json and persist across bot restarts. A background cleanup task runs every 6 hours, pruning sessions and worktrees older than 7 days.
node index.mjsnpm startFor persistent deployment on a Linux server, see the full systemd setup guide. The short version:
sudo cp fixbot.service.example /etc/systemd/system/fixbot.service
# Edit the file to set your paths and user
sudo systemctl daemon-reload
sudo systemctl enable --now fixbotThe guide covers creating a dedicated user, authenticating Claude and GitHub CLI as that user, handling nvm-installed Node.js, and troubleshooting common service issues.
You can also use PM2, forever, or any process manager:
# PM2
npm install -g pm2
pm2 start index.mjs --name fixbot
pm2 save
pm2 startupfixbot runs Claude with --dangerously-skip-permissions, which means Claude can execute arbitrary commands in the worktree. To limit risk:
- Run under a dedicated user with minimal system access
- Scope your GitHub token — use a fine-grained PAT with access only to the target repo
- Restrict the Discord channel — only trusted team members should have access to the fixbot channel
- Review PRs before merging — fixbot creates PRs but never merges them without a human reaction
- Use quality gates — configure comprehensive CI commands so Claude validates its own changes
The bot only operates on isolated git worktrees and only pushes ai/* branches, so your main branches are never at risk of direct modification.
Your .env file is missing a required value, or the process can't read it. Double-check that DISCORD_BOT_TOKEN, DISCORD_CHANNEL_ID, GITHUB_TOKEN, and TARGET_REPO are all set. If using systemd, verify the EnvironmentFile path in your service file.
- Verify
GITHUB_TOKENhas read access to your issues repo - Check that the issue number exists and isn't in a private repo your token can't access
- If using
ISSUES_REPO, make sure it's set correctly
Increase JOB_TIMEOUT_MS. The default is 30 minutes (1800000ms). Complex issues with large codebases may need more time:
JOB_TIMEOUT_MS=3600000 # 1 hourThe GitHub CLI needs to be authenticated on the machine running fixbot. Verify with:
gh auth statusClaude uses gh pr create to open pull requests, so gh must have push access to the target repo.
- Verify
DISCORD_CHANNEL_IDmatches the channel you're posting in - Check that the bot has the Message Content privileged intent enabled in the Discord developer portal
- Confirm the bot is online in Discord (green dot on its avatar)
- Check the console logs for errors
- Make sure
gh auth statusshows the correct account with push access - Verify the target branch exists on the remote (
git ls-remote origin) - Check Claude's output in the Discord result message for error details
If you see git worktree errors after a crash, clean up manually:
cd workspace/<your-repo>
git worktree pruneContributions are welcome. Please open an issue first to discuss what you'd like to change.
- Fork the repo
- Create a feature branch (
git checkout -b my-feature) - Make your changes
- Test locally with your own Discord bot and repo
- Open a pull request