autopilot-code
v2.2.2
Published
Repo-issue–driven autopilot runner
Readme
autopilot
Repo-issue–driven autopilot runner.
What it is
Autopilot is a GitHub-issue–driven automation loop:
- repos opt in by committing
.autopilot/autopilot.json - work lives in GitHub Issues
- the runner advances issues through a label-based workflow
- the runner is designed to determine “is work still happening?” using durable artifacts (GitHub + repo files), not process inspection (
ps, PID checks, etc.)
Repo opt-in
A repo is considered autopilot-enabled when it contains:
.autopilot/autopilot.jsonwithenabled: true
Quick Start
Choosing an Agent
Autopilot supports multiple agent types:
opencode (recommended): Full-featured agent with extensive code understanding
{
"agent": "opencode"
}claude: Fast, targeted code changes
{
"agent": "claude"
}Runner Configuration
IMPORTANT: The new Python runner is now the default. The legacy bash script is deprecated and will be removed in a future version.
The new runner provides enhanced progress tracking and session continuity:
{
"enablePlanningStep": true
}Deprecation Timeline:
- Current release: New runner is default, deprecation warnings added
- +1 minor release: More prominent warnings for legacy runner
- +1 major release: Bash script will be removed entirely
If you see a deprecation warning, it means you're using the legacy bash runner. To migrate, remove "useNewRunner": false from your config (or set to true).
Understanding Step Labels
When using the new runner, issues progress through these labels:
autopilot:planning- Creating implementation planautopilot:implementing- Writing codeautopilot:pr-created- Pull request createdautopilot:waiting-checks- Waiting for CIautopilot:fixing-checks- Fixing failing CIautopilot:merging- Merging PR
To create these labels in your repository:
autopilot setup-labels --repo owner/repoExample:
{
"enabled": true,
"repo": "bakkensoftware/autopilot",
"agent": "opencode",
"autoMerge": true,
"mergeMethod": "squash",
"allowedMergeUsers": ["github-username"],
"issueLabels": {
"queue": ["autopilot:todo"],
"blocked": "autopilot:blocked",
"inProgress": "autopilot:in-progress",
"done": "autopilot:done"
},
"priorityLabels": ["p0", "p1", "p2"],
"minPriority": null,
"ignoreIssueLabels": ["autopilot:backlog"],
"maxParallel": 1,
"heartbeatMaxAgeSecs": 3600,
"branchPrefix": "autopilot/",
"allowedBaseBranch": "main",
"autoResolveConflicts": true,
"conflictResolutionMaxAttempts": 3,
"autoFixChecks": true,
"autoFixChecksMaxAttempts": 3
}Notes:
repomust be the GitHubowner/name.agent(optional, default"opencode"): set to"opencode"or"claude"to choose which coding agent to use.autoMerge(optional, defaulttrue): iftrue, autopilot will automatically merge PRs after checks pass.mergeMethod(optional, default"squash"): merge strategy to use. Options:"squash","merge", or"rebase".allowedMergeUsers(required whenautoMerge=true): list of GitHub usernames allowed to auto-merge. The runner verifies the authenticated GitHub user is in this list before merging.minPriority(optional, defaultnull): minimum priority to work on. For example, set to"p1"to only work onp0andp1issues. UsespriorityLabelsarray for priority order.ignoreIssueLabels(optional, default["autopilot:backlog"]): issues with any of these labels will be ignored by the runner.autoResolveConflicts(optional, defaulttrue): iftrue, autopilot will attempt to automatically resolve merge conflicts.conflictResolutionMaxAttempts(optional, default3): maximum number of attempts to resolve merge conflicts.autoFixChecks(optional, defaulttrue): iftrue, autopilot will attempt to automatically fix failing CI checks.autoFixChecksMaxAttempts(optional, default3): maximum number of attempts to fix failing checks.enablePlanningStep(optional, defaulttrue): iftrue, add an explicit planning phase before implementation.agentPath(optional): custom path to agent executable (defaults to searching PATH).
Workflow (labels)
Autopilot uses labels as a kanban state machine:
autopilot:backlog— captured, not readyautopilot:todo— ready to be picked up by the runnerautopilot:in-progress— claimed by autopilotautopilot:blocked— needs human input or missing/stale heartbeatautopilot:done— completed
Optional priority labels:
p0,p1,p2(lower number = higher priority)
How claiming works
When the runner finds a candidate issue (typically autopilot:todo):
- It applies
autopilot:in-progress - It removes the queue label(s) (e.g.
autopilot:todo) - It leaves a comment indicating the claim time and next step
Durable tracking (no process inspection)
This runner intentionally does not check local processes to decide if work is ongoing.
Instead it uses durable artifacts:
- GitHub: issue labels + issue comments + (future) PR presence/status
- Repo file heartbeat:
.autopilot/state.json
The runner writes/updates .autopilot/state.json like:
{
"activeIssue": {
"number": 2,
"repo": "bakkensoftware/autopilot",
"updatedAt": 1738000000
}
}On each loop:
- if an issue is
autopilot:in-progressbut the heartbeat is stale/missing, autopilot comments and moves it toautopilot:blocked.
Running locally
Python runner
# Run a single scan/claim/act cycle
python3 scripts/run_autopilot.py --root /mnt/f/Source
# Run in foreground loop mode (dev-friendly)
python3 scripts/run_autopilot.py --root /mnt/f/Source --interval-seconds 60Node CLI wrapper
From the repo root:
npm install
npm run build
# sanity checks
node dist/cli.js doctor
# scan enabled repos without claiming
node dist/cli.js scan --root /mnt/f/Source
# claim exactly one issue + comment
node dist/cli.js run-once --root /mnt/f/Source
# run service in foreground mode (dev-friendly)
node dist/cli.js service --foreground --interval-seconds 60 --root /mnt/f/SourceThe foreground service mode runs continuously with the specified interval and logs to stdout. Press Ctrl+C to shut down cleanly.
Roadmap
- ~~Spawn a coding agent (Claude Code / OpenCode) in a worktree per issue~~ (done)
- ~~Create PRs linked to issues; wait for checks to go green~~ (done)
- ~~Merge PRs automatically when mergeable + checks pass~~ (done)
- Close issues + apply
autopilot:done
Config template
See templates/autopilot.json.
