Skip to content
/ fixbot Public

Autonomous AI issue fixer — Discord bot powered by Claude Code CLI

License

Notifications You must be signed in to change notification settings

cj-vana/fixbot

Repository files navigation

fixbot

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.

Why fixbot?

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.

How it works

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.

Prerequisites

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:

Installation

1. Clone the repo

git clone https://github.com/cj-vana/fixbot.git
cd fixbot

2. Install dependencies

npm install

3. Configure environment

cp .env.example .env

Open .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/repo

See the full configuration reference below for all available options.

4. Start fixbot

node index.mjs

You should see:

fixbot starting...
fixbot online as fixbot#1234
fixbot ready

Configuration

All configuration is via environment variables in .env.

Required

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)

Optional

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

Commands

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

Examples

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.

Customization

Quality gates

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

Multiple target branches

If your repo uses staging and production branches:

TARGET_BRANCHES=develop,main

Running fix 42 creates separate PRs targeting each branch. Running fix 42 develop targets only that branch.

Separate issues repo

If your issues live in a different repo from your code:

TARGET_REPO=myorg/myapp
ISSUES_REPO=myorg/myapp-issues

Architecture

index.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)

Flow

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

Branch safety

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

Session lifecycle

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.

Deployment

Run directly

node index.mjs

Run with npm

npm start

Run as a systemd service (Linux)

For 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 fixbot

The guide covers creating a dedicated user, authenticating Claude and GitHub CLI as that user, handling nvm-installed Node.js, and troubleshooting common service issues.

Run with a process manager

You can also use PM2, forever, or any process manager:

# PM2
npm install -g pm2
pm2 start index.mjs --name fixbot
pm2 save
pm2 startup

Security considerations

fixbot 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.

Troubleshooting

"Missing required env var"

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.

"Could not fetch issue"

  • Verify GITHUB_TOKEN has 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

Claude times out

Increase JOB_TIMEOUT_MS. The default is 30 minutes (1800000ms). Complex issues with large codebases may need more time:

JOB_TIMEOUT_MS=3600000  # 1 hour

"Permission denied (publickey)"

The GitHub CLI needs to be authenticated on the machine running fixbot. Verify with:

gh auth status

Claude uses gh pr create to open pull requests, so gh must have push access to the target repo.

Bot doesn't respond

  • Verify DISCORD_CHANNEL_ID matches 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

PRs aren't being created

  • Make sure gh auth status shows 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

Worktree errors

If you see git worktree errors after a crash, clean up manually:

cd workspace/<your-repo>
git worktree prune

Contributing

Contributions are welcome. Please open an issue first to discuss what you'd like to change.

  1. Fork the repo
  2. Create a feature branch (git checkout -b my-feature)
  3. Make your changes
  4. Test locally with your own Discord bot and repo
  5. Open a pull request

License

MIT

About

Autonomous AI issue fixer — Discord bot powered by Claude Code CLI

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages