Autonomous GitHub issue processor powered by any CLI tool. Watches repos for open issues, dispatches a command to resolve them, and pushes fixes or opens PRs.
Via Homebrew:
brew install --cask tinybluerobots/tap/issuebotVia mise:
mise use -g go:github.com/tinybluerobots/issuebotVia go install:
go install github.com/tinybluerobots/issuebot@latestOr download a prebuilt binary from the latest release.
# Watch current directory's repo with Claude
issuebot --command "claude -p {prompt} --dangerously-skip-permissions"
# Work directly in current directory (no clone)
issuebot --local --command "claude -p {prompt} --dangerously-skip-permissions"
# Watch a single repo with Copilot
issuebot --repo owner/repo --command "copilot -p {prompt} --yolo"
# Watch all repos in an org with Gemini
issuebot --org myorg --command "gemini -p {prompt} --yolo"
# Only process issues labelled "bug"
issuebot --repo owner/repo --label bug --command "claude -p {prompt} --dangerously-skip-permissions"
# Only process issues created by a specific user
issuebot --repo owner/repo --author octocat --command "claude -p {prompt} --dangerously-skip-permissions"
# Preview what the command would do without pushing
issuebot --repo owner/repo --dry-run --command "claude -p {prompt} --dangerously-skip-permissions"
# Open PRs instead of pushing directly
issuebot --repo owner/repo --strategy pr --command "copilot -p {prompt} --yolo"
# Poll every 5 minutes with 10 workers
issuebot --org myorg --interval 5m --workers 10 --command "gemini -p {prompt} --yolo"
# Get notified via ntfy.sh
issuebot --repo owner/repo --ntfy-topic my-alerts --command "copilot -p {prompt} --yolo"
# Log to a file for background operation
issuebot --org myorg --log-file ~/.issuebot/issuebot.log --command "claude -p {prompt} --dangerously-skip-permissions"
# Use a custom script
issuebot --local --command "./my-issue-handler.sh {prompt}"| Flag | Default | Description |
|---|---|---|
--org |
GitHub org to watch (all non-archived source repos) | |
--repo |
Single owner/repo to watch |
|
--label |
Only process issues with this label (default: all open issues) | |
--author |
Only process issues by this GitHub username | |
--strategy |
commit |
Git strategy: pr (branch + PR) or commit (push to default branch) |
--interval |
30s |
Polling interval |
--workers |
5 |
Max concurrent repo workers |
--workspace |
~/.issuebot/repos |
Directory for cloned repos |
--local |
false |
Use current directory instead of cloning |
--command |
(required) | Command to run ({prompt} is replaced with the rendered prompt) |
--prompt-file |
~/.issuebot/prompt.tmpl |
Path to prompt template file |
--dry-run |
false |
Run command but skip push/PR (print diff instead) |
--max-retries |
3 |
Max retry attempts per issue |
--log-file |
Log file path (defaults to stdout) | |
--ntfy-topic |
ntfy.sh topic for notifications |
Requires a GitHub token. Set GITHUB_TOKEN or run gh auth login.
- Polls GitHub API for open issues (optionally filtered by label)
- Clones the repo (or uses current dir with
--local) - Dispatches the configured command with the issue as a prompt
- Opens a PR or pushes directly, depending on strategy
- Tracks state in
~/.issuebot/state.jsonto avoid re-processing
Each repo gets at most one concurrent worker to prevent conflicts — if a repo already has an issue in progress, other issues for that repo are skipped until the next poll cycle. The --workers flag controls how many repos can be processed in parallel. For a single repo, only one issue runs at a time regardless of the worker count. For orgs with many repos, increasing --workers lets more repos be served simultaneously. Failed issues are retried up to --max-retries times.
The prompt template controls what your command receives as input. On first run, issuebot writes a default template to ~/.issuebot/prompt.tmpl. Use --prompt-file to specify a different path.
The template uses Go's text/template syntax. The rendered output replaces {prompt} in your --command.
Default template:
You are working through a GitHub issue queue autonomously.
The repo is: {{.Repo}}
GitHub Issue #{{.Number}}: {{.Title}}
{{.Body}}
Steps:
1. Read the issue carefully. Implement the changes needed to resolve it.
2. Run tests (check package.json / Makefile / go.mod for how).
3. Commit with a descriptive message referencing the issue number.
4. Push to origin.
5. Close the issue: gh issue close {{.Number}} --repo {{.Repo}} --comment "Resolved in $(git rev-parse --short HEAD)"
6. Output exactly: ISSUE_RESOLVED #{{.Number}}
If you cannot resolve the issue, output exactly: ISSUE_FAILED #{{.Number}} with a brief explanation.
The ISSUE_FAILED marker is important — if the command output contains it, issuebot marks the issue as failed and retries later.
Available fields:
| Field | Description |
|---|---|
{{.Repo}} |
Repository full name (owner/repo) |
{{.Number}} |
Issue number |
{{.Title}} |
Issue title |
{{.Body}} |
Issue body |
{{.Author}} |
Issue author's GitHub username |
{{.Labels}} |
Comma-separated list of issue labels |
{{.DefaultBranch}} |
Repository default branch |
The --command flag is required. Use {prompt} as a placeholder — issuebot substitutes it with the rendered prompt template:
# Claude Code
issuebot --command "claude -p {prompt} --dangerously-skip-permissions"
# GitHub Copilot
issuebot --command "copilot -p {prompt} --yolo"
# Google Gemini CLI
issuebot --command "gemini -p {prompt} --yolo"Override defaults per issue by adding a config block to the issue body:
<!-- issuebot
strategy: commit
branch: custom-branch-name
-->Example with post-command to request review after PR creation:
<!-- issuebot
strategy: pr
post-command: gh pr edit $PR_NUMBER --add-reviewer octocat
-->| Key | Values | Description |
|---|---|---|
strategy |
pr, commit |
Override the default git strategy |
branch |
any string | Custom branch name (default: issuebot/issue-{N}) |
post-command |
any command | Shell command to run after PR creation ($PR_URL and $PR_NUMBER available) |
Create a user service:
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/issuebot.service << 'EOF'
[Unit]
Description=issuebot
[Service]
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/local/bin/issuebot --org myorg --author myuser --ntfy-topic my-alerts --command "claude -p {prompt} --dangerously-skip-permissions"
Restart=on-failure
[Install]
WantedBy=default.target
EOFEnable and start:
systemctl --user daemon-reload
systemctl --user enable --now issuebotEnable lingering so the service survives logout:
sudo loginctl enable-linger $USERCheck status:
systemctl --user status issuebot
journalctl --user -u issuebot -fcat > ~/Library/LaunchAgents/com.issuebot.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.issuebot</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/issuebot</string>
<string>--org</string><string>myorg</string>
<string>--author</string><string>myuser</string>
<string>--ntfy-topic</string><string>my-alerts</string>
<string>--command</string><string>claude -p {prompt} --dangerously-skip-permissions</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
</dict>
</plist>
EOF
launchctl load ~/Library/LaunchAgents/com.issuebot.plistissuebot works best when issues are atomic and self-contained — each issue should include enough context for the AI to complete the work without guessing.
Recommended workflow:
- Plan first — use your AI tool's planning capabilities to design the feature or fix
- Create atomic issues — break the plan into small, focused issues where each one contains the relevant context (files to change, acceptance criteria, dependencies). This repo includes a plan-to-issues skill you can install locally in your AI tool to automate this
- Let issuebot work — it picks up each issue, resolves it independently, and pushes the result
Vague issues like "refactor the auth system" will produce vague results. An issue that says "extract validateToken from auth.go into a new token.go file, update imports in server.go and middleware.go, run mise run test" gives the AI everything it needs.
Requires mise:
mise run all # format + lint + test
mise run lint # golangci-lint
mise run test # gotestsum
mise run build # build binary