autopilot-code
v2.9.1
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.)
How This Fits in the Timeline
Autopilot's first commit was January 27, 2026. The concept of autonomous "issue-to-PR" coding agents had been explored for a few years by then, but the space was still rapidly evolving and the major platforms had only just shipped their versions months earlier:
| Date | Project | |---|---| | Jun 2023 | Aider — one of the first agentic CLI coding tools (terminal-based pair programming) | | Jul 2023 | Sweep AI (YC S23) — the first major "GitHub issue to PR" bot | | Mar 2024 | Devin (Cognition Labs) — "first AI software engineer," cloud-sandboxed. GA December 2024 at $500/month. | | Apr 2024 | SWE-Agent (Princeton) — takes a GitHub issue and tries to fix it automatically | | Apr 2024 | GitHub Copilot Workspace technical preview — issue-to-code workflow with human review | | Feb 2025 | Claude Code research preview (Anthropic) | | May 2025 | GitHub Copilot Coding Agent announced at Build — assign an issue to Copilot, it creates a branch, implements, opens PR. Closest major-platform equivalent to autopilot. | | May 2025 | OpenAI Codex Agent — cloud-based autonomous coding agent | | Aug 2025 | Google Jules GA — autonomous coding agent built on Gemini 2.0 | | Jan 2026 | This project (autopilot) — self-hosted, agent-agnostic, issue-driven runner with label-based state machine |
Autopilot arrived after the concept was validated by Sweep, Devin, and the major platforms, but differentiates itself architecturally in ways none of the above do: it is self-hosted (not a cloud SaaS), agent-agnostic (supports OpenCode, Claude Code, or any future agent), uses durable artifact tracking via GitHub labels and heartbeat files rather than process inspection, and runs as a continuous service monitoring a portfolio of repos — not a one-shot tool. The closest comparison is GitHub Copilot Coding Agent, but autopilot is open and infrastructure-independent rather than locked to GitHub's pricing and platform.
Repo opt-in
A repo is considered autopilot-enabled when it contains:
.autopilot/autopilot.jsonwithenabled: true
Quick Start
For a full step-by-step installation and setup walkthrough (prerequisites,
npm install -g autopilot-code, autopilot init, service installation on
Linux/macOS, WSL notes, and troubleshooting), see docs/INSTALL.md.
For the day-to-day CLI reference, the label workflow/lifecycle, configuring AI agents, managing the service, and monitoring/debugging a run, see docs/USAGE.md.
Choosing an Agent
Autopilot supports multiple agent types:
opencode (recommended): starts an opencode serve HTTP server per worktree and
drives it via OpenCode's HTTP API, which allows the runner to resume a specific
prior session (unlike opencode run). "opencode-server" is accepted as a
compatible alias for the same value.
{
"agent": "opencode"
}Requirements:
opencodemust be resolvable onPATH(or setagentPathto a specific binary).- A model with available credit/quota. Set
agentModelto"provider/model"(for example"github-copilot/claude-sonnet-5") to pin the model for this repo; the runner then sends it with every prompt. WhenagentModelis unset, the server uses the model from the target repo's ownopencode.jsonat its root, and when that is absent too, OpenCode's global default.
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,
"enableScheduler": false,
"schedulerTimeoutSecs": 60
}Notes:
repomust be the GitHubowner/name.agent(optional, default"opencode"): set to"opencode"(alias"opencode-server") or"claude"to choose which coding agent to use."opencode"starts anopencode serveHTTP server per worktree; see "Choosing an Agent" above for requirements.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.enableScheduler(optional, defaultfalse): iftrue, ask the configured agent to reorder queued candidate issues before claiming (e.g. to work an issue that unblocks others first, or push a vague issue to the back). Only used when there is more than one candidate issue to claim. A scheduling failure or timeout falls back to the original order and never stops a cycle.schedulerTimeoutSecs(optional, default60): how long to wait for the scheduler agent's response before giving up and using the original claim order.agentPath(optional): custom path to agent executable (defaults to searching PATH).agentModel(optional): OpenCode model as"provider/model"(for example"github-copilot/claude-sonnet-5"), sent with every prompt when set. Empty or missing falls back to the target repo's rootopencode.json, then to OpenCode's global default. A value without a/fails the step before any request is sent.
If a repo's autopilot.json omits agent and/or agentModel, the Python
runner (scripts/run_autopilot.py) falls back to defaultAgent /
defaultAgentModel from the global config (~/.config/autopilot/config.json,
see Global config below) before falling back further to
opencode / unset. The repo-level value always wins when present.
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.
Upgrading
Update to the latest published version with:
autopilot upgradeThis reads the installed version from the CLI's own package.json, checks
the latest version published to npm (npm view autopilot-code version), and:
- exits immediately with an "already up to date" message if they match
- otherwise refuses to upgrade if any configured repo has an issue currently
in progress (checked via each repo's
.autopilot/state.jsonactiveIssues), unless--forceis passed - runs
npm install -g autopilot-code@latestand verifies the new version was actually installed before reporting success
# only compare versions, don't install anything
autopilot upgrade --check
# upgrade even if an issue is currently in progress
autopilot upgrade --forceThe npm package's postinstall script already regenerates the systemd unit
file and restarts the service if it was running, so autopilot upgrade does
not restart the service itself — it just reports that this already happened.
Upgrades are always manual. The running service never installs or restarts
itself: while running, it only checks (at startup and then at most once per
hour) whether a newer version is published, and logs a single WARNING
naming both versions and the autopilot upgrade command when one is found.
It never acts on that warning itself — a person decides when to run
autopilot upgrade. Set updateCheckEnabled to false in the global config
to disable this check entirely (see Global config below).
Global config
Autopilot keeps a small global config file at ~/.config/autopilot/config.json
(created by autopilot init). It currently holds:
sourceFolders— repo root directories autopilot scans (array of paths)defaultAgent— optional default agent name (opencodeorclaude; falls back tonone/unusable if set to anything else the runner doesn't recognize). Used by the Python runner as the fallbackagentfor any repo whose.autopilot/autopilot.jsonomitsagent; the repo's own value always takes precedence.defaultAgentModel— optional default"provider/model"string, used as the fallbackagentModelthe same way (repo value wins if present).updateCheckEnabled— optional boolean, defaulttrue. Controls the runner's periodic "a newer autopilot is published" warning described above. Set tofalseto silence the check entirely; this has no effect onautopilot upgrade, which always works when run explicitly.
Manage it with the autopilot config subcommands instead of editing the JSON
by hand:
# show the whole file
autopilot config list
# read one value
autopilot config get sourceFolders
# write a value (comma-separated for array keys; booleans/numbers auto-parsed)
autopilot config set defaultAgent claude
autopilot config set sourceFolders /path/to/repo-a,/path/to/repo-b
# remove a key
autopilot config unset defaultAgent
# open the file in $EDITOR
autopilot config editget/set/unset warn (but do not refuse) if the key isn't one of the known
keys above, so experimental/future fields can still be set manually.
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.
Validating autopilot.json
autopilot.json is validated automatically:
- On
autopilot init, right after the config file is written. - On every runner cycle (
load_configinscripts/run_autopilot.py), so a bad edit made by hand is caught the next time the service polls.
You can also validate a config manually at any time:
# validates .autopilot/autopilot.json in the current directory
autopilot validate
# validate an arbitrary file
autopilot validate path/to/autopilot.jsonValidation checks required fields (repo, in owner/repo format), field
types (booleans, positive integers, string arrays), valid enum values
(mergeMethod, agent), agentModel shape (provider/model), and that
label fields are non-empty strings. It never crashes the runner: a repo
whose config fails validation is still scanned (for blocked-marking, merge
follow-up, etc.) but will not claim new issues until the problems reported
are fixed — see scripts/issue_runner/config_schema.py for the full rule
set, and tests/test_config_schema.py for examples of valid/invalid
configs.
