Skip to content

About

AI issue-to-PR agent backend: GitHub webhook trigger, safety gates, durable MySQL task queue, guarded repo tools, adapter-driven test verification, and pull requests for human review. Java 21, Spring Boot.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

PatchPilot

CI

PatchPilot is a self-hosted GitHub issue-to-PR agent. Comment /agent fix ... on an issue, and PatchPilot clones the repository, plans and applies a patch with an LLM, runs the repository's own test command, and opens a pull request for a human to review.

It is a backend system, not a chatbot: every step is a durable task record with a timeline, tool-call and model-call audit, captured test output, and safety gates that decide whether the run may continue. It never pushes to a default branch and never merges.

  • Stack: Java 21, Spring Boot 3.5, MySQL 8 + Flyway, MyBatis-Plus, an OpenAI-compatible model client, React 19 + Vite + TypeScript dashboard, Docker Compose.
  • Tests: about 700 JUnit tests for the backend and 120 Vitest tests for the dashboard, both run by GitHub Actions.

How a fix runs

sequenceDiagram
    participant GH as GitHub
    participant WH as Webhook + safety gates
    participant Q as MySQL task queue
    participant W as Worker
    participant LLM as OpenAI-compatible model
    GH->>WH: issue_comment "/agent fix ..."
    WH->>WH: verify HMAC signature, allowlists, rate limit, trigger policy
    WH->>Q: create durable task
    WH-->>GH: status comment: accepted
    W->>Q: claim task (visibility timeout, retries)
    W->>W: clone repo, create branch, detect build system
    W->>LLM: fix plan as JSON (summary, target files, steps, risk)
    W->>LLM: full-file edits for the planned files
    W->>LLM: independent patch review (approve / reject)
    W->>W: diff risk gate, then allowlisted verification (e.g. mvn test)
    W->>GH: push branch, open pull request, update status comment
Loading
  1. Trigger. A GitHub issue_comment webhook arrives. The signature is verified, then the comment passes trigger-user and repository allowlists, per-user/repository/issue rate limits, an automatic quarantine for repeat offenders, and a deterministic trigger policy that rejects vague ("fix it") or dangerous ("delete the production database") instructions. Rejections are recorded and answered on the issue with a safe refusal comment.
  2. Durable task. Accepted triggers become a fix_task row plus a queue item in MySQL. A poller claims work with a visibility timeout, records worker heartbeats, retries transient failures with backoff, and recovers tasks that a crashed worker left behind. Every state change is a timeline event.
  3. Workspace. The worker clones the repository into an isolated task directory, creates a patchpilot/<task-id> branch, and detects the build system with a language adapter (15 adapters, table below). Unsupported repositories fail fast before any model call.
  4. Patch. The plan-driven workflow makes three model calls: a JSON fix plan, full-file edits limited to the planned files (at most three), and an independent patch review that can reject the edits. A workspace path guard blocks protected files and symlink escapes; a diff risk gate blocks secret-like added lines, binary patches, protected paths, and overly broad patches and parks the task as PENDING_REVIEW for an explicit operator approval.
  5. Verify. The adapter's verification command (mvn test, npm test, cargo test, ...) runs from a fixed allowlist. Model output can never choose the command. Output, exit code, and duration are stored with the task.
  6. Publish. On green tests the worker commits as the PatchPilot author, pushes the branch, opens a pull request whose body lists the adapter, verification results, and review evidence, and updates the issue status comment with the PR link. Failures are reported on the issue with a failure category and next action.

Live demo

The demo target is bingqin2/patchpilot-demo-java, a tiny Maven project whose Calculator.divide throws a bare ArithmeticException on a zero divisor while a test expects IllegalArgumentException("divisor must not be zero"). Issue #1 describes the expected behaviour; PatchPilot answered it with pull request #2.

The run was executed by the Docker Compose stack on a laptop with Tencent Hy3 as the model provider (reasoning_effort=high):

Time Step Evidence
0 s Trigger accepted, task created and claimed by the worker timeline events TRIGGER_ACCEPTED, TASK_CREATED, RUNNING
1 s Clone, branch patchpilot/<task-id>, adapter java/maven detected, issue context loaded tool calls WorkspaceService, LanguageAdapterRegistry, IssueContextService
1 s to 32 s Fix plan, full-file edit of Calculator.java, independent review (APPROVE, confidence HIGH) three recorded model calls with prompts, replies, and token counts
33 s Diff risk gate passed (three added lines, no secrets or protected paths) tool calls DiffTool, GeneratedDiffRiskGate
33 s to 44 s mvn test in the worker container: 5 tests, 0 failures, exit code 0 captured test run with full output
47 s Commit, push, pull request opened with the verification and review evidence in its body PR_CREATED, COMPLETED

PatchPilot dashboard after the demo run: metrics, model usage, and the completed task with its pull request link

The resulting diff is the three-line guard the issue asked for and nothing else. The first attempt at this run failed at the review gate because Hy3 returned its confidence as a number rather than a string; that failure, the parser fix, and the retry are all recorded in the same task history, which is the point of keeping every step durable.

Quick start (Docker Compose)

Prerequisites: Docker Desktop, a GitHub repository to test on, a fine-grained GitHub token for that repository with Contents: read and write, Issues: read and write, Pull requests: read and write, and Metadata: read, an API key for any OpenAI-compatible chat-completions endpoint, and a tunnel tool such as cloudflared or ngrok so GitHub can reach your machine.

git clone https://github.com/bingqin2/PatchPilot.git
cd PatchPilot
cp .env.example .env
# edit .env: PATCHPILOT_GITHUB_TOKEN, PATCHPILOT_GITHUB_WEBHOOK_SECRET,
#            PATCHPILOT_ADMIN_TOKEN, PATCHPILOT_AGENT_API_KEY (and model/base URL)
docker compose --env-file .env up --build

This starts MySQL, the backend on http://127.0.0.1:8080, and the dashboard on http://127.0.0.1:5173. Check the backend:

curl http://127.0.0.1:8080/health
curl -H "X-PatchPilot-Admin-Token: $PATCHPILOT_ADMIN_TOKEN" http://127.0.0.1:8080/api/configuration/summary

Expose the webhook endpoint and register it on GitHub:

cloudflared tunnel --url http://127.0.0.1:8080

In the test repository open Settings → Webhooks → Add webhook: payload URL https://<your-tunnel-host>/api/github/webhook, content type application/json, secret equal to PATCHPILOT_GITHUB_WEBHOOK_SECRET, and only the Issue comments event.

Now open an issue and comment:

/agent fix Add null handling to Calculator.divide and cover it with a test

PatchPilot answers on the issue within a few seconds, and the dashboard shows the task moving through RUNNING, RUNNING_TESTS, PR_CREATED, and COMPLETED. For a deterministic smoke test that does not depend on model quality, use the built-in replacement instruction on an existing file:

/agent fix replace docs/demo.md PatchPilot smoke test

The docs/demo-repositories/ folder contains a minimal fixture for every supported build system; push one of them to a test repository to try the full loop on a small project.

Supported repositories

Build system Detected by Verification command
Java / Maven pom.xml mvn test (or ./mvnw test)
Java / Gradle build.gradle, build.gradle.kts, gradlew gradle test (or ./gradlew test)
Go go.mod go test ./...
Rust / Cargo Cargo.toml cargo test
Node / npm, pnpm, yarn, Bun package.json with scripts.test plus the lockfile npm test, pnpm test, yarn test, bun test
Python / pytest pytest config or dependency python3 -m pytest
Python / Poetry, uv pyproject.toml tool section plus pytest poetry run pytest, uv run pytest
Python / tox, nox, hatch tox.ini, noxfile.py, hatch config tox, nox, hatch test
Custom profile .patchpilot/verification-profile.json any command from the allowlist above

Adapters are explicit classes under language/impl; adding one means implementing detection and returning an allowlisted command. The backend Docker image ships all of these toolchains so verification can run inside the container.

Configuration

Copy .env.example to .env. The important variables:

Variable Purpose
PATCHPILOT_GITHUB_TOKEN Fine-grained token used to clone, push, comment, and open pull requests.
PATCHPILOT_GITHUB_WEBHOOK_SECRET HMAC secret shared with the GitHub webhook.
PATCHPILOT_GITHUB_WEBHOOK_PUBLIC_BASE_URL Optional public base URL of the backend, shown in the dashboard.
PATCHPILOT_ADMIN_TOKEN Required header X-PatchPilot-Admin-Token for every /api/* call except the webhook.
PATCHPILOT_AGENT_BASE_URL, PATCHPILOT_AGENT_MODEL, PATCHPILOT_AGENT_API_KEY OpenAI-compatible provider (defaults to https://api.openai.com/v1 and gpt-4.1-mini).
PATCHPILOT_AGENT_REASONING_EFFORT Optional reasoning_effort value sent with every model call, for providers that support it (for example high on Tencent Hy3). Leave empty to omit the field.
PATCHPILOT_ALLOWED_TRIGGER_USERS, PATCHPILOT_ALLOWED_REPOSITORIES Comma-separated allowlists. Empty means unrestricted, which is only sensible for local use.
PATCHPILOT_TRIGGER_RATE_LIMIT_*, PATCHPILOT_REJECTED_TRIGGER_QUARANTINE_* Abuse controls, on by default.
PATCHPILOT_MODEL_TRIGGER_CLASSIFICATION_ENABLED Optionally ask the model whether a request is concrete enough before creating a task.
PATCHPILOT_REVIEW_APPROVAL_ALLOWED_OPERATORS Who may approve a PENDING_REVIEW task.
PATCHPILOT_DASHBOARD_BASE_URL Optional dashboard URL linked from issue comments and PR bodies.

Profiles: docker (Compose, MySQL), local (MySQL on 127.0.0.1:3307, for running the backend from an IDE against the Compose database), and default (no database, in-memory stores, for quick local runs and tests).

Dashboard and API

The dashboard is an operations view: status counts and metrics, the task list with filters and search, task detail (timeline, generated diff, verification output, tool calls, model calls with token usage and latency), cancel / retry / approve-review actions, a manual task form with a read-only trigger dry run, queue and worker health, recent webhook deliveries, rejected triggers and quarantines, configuration and credential readiness, and the supported adapters.

Everything the dashboard shows comes from the JSON API under /api, for example:

H="X-PatchPilot-Admin-Token: $PATCHPILOT_ADMIN_TOKEN"
curl -H "$H" "http://127.0.0.1:8080/api/tasks?status=FAILED&limit=20"
curl -H "$H" http://127.0.0.1:8080/api/tasks/{taskId}/detail
curl -H "$H" http://127.0.0.1:8080/api/tasks/{taskId}/test-runs
curl -H "$H" http://127.0.0.1:8080/api/task-queue/summary
curl -H "$H" http://127.0.0.1:8080/api/rejected-triggers
curl -H "$H" -X POST http://127.0.0.1:8080/api/tasks/{taskId}/approve-review \
     -H "Content-Type: application/json" -d '{"operator":"you","reason":"diff reviewed"}'

Safety boundaries

PatchPilot must not, and is tested to not:

  • push to a default branch or merge pull requests;
  • run any shell command produced by the model (verification commands come from the adapter allowlist, and the command guard also pins the exact git invocations);
  • read or write outside the task workspace, or touch protected paths such as .env files, .git, .github/workflows, package-manager credential files, and private keys;
  • continue to verification, commit, push, or PR creation after the diff risk gate or the model patch review rejects the change;
  • accept triggers from users or repositories outside the configured allowlists, or drop a rejected trigger without an audit record;
  • log or echo secrets, including in issue comments and PR bodies.

Architecture

A single Spring Boot process runs the webhook API, the queue poller, and the worker. See docs/architecture.md for the module map, the task state machine, and the data model.

PatchPilot/src/main/java/io/patchpilot/backend
  github/        webhook controller + service, signature verifier, REST clients, credential probes
  safety/        trigger policy, allowlists, rate limits, quarantine, diff risk gate, audit records
  task/          FixTask model, MySQL/in-memory stores, queue poller, worker, WorkspaceFixTaskExecutor
  workspace/     git clone / branch / recovery inside the workspace root, path guard
  agent/         OpenAI-compatible client, FixPlan -> FileEditPlan -> PatchReview workflow, tools
  language/      15 build-system adapters and the adapter registry
  runner/        verification runner, command allowlist, command execution guard
  security/      admin token filter
  configuration/ dashboard/ observability/
frontend/        React dashboard (Vite, Vitest)
docs/            architecture, code standard, decision log, demo fixture repositories

Development

# backend tests (no database needed, about 10 s)
mvn -pl PatchPilot test

# backend without Docker: in-memory stores, port 8080
cd PatchPilot && ./mvnw spring-boot:run

# dashboard dev server on http://127.0.0.1:5173 (proxies /api to the backend)
cd frontend && npm install && npm run dev

# dashboard type-check, build, tests
cd frontend && npm run build && npm test

If you used an earlier PatchPilot database, recreate it with docker compose down -v; the migration set was renumbered when the project was reorganized.

Limitations and next steps

  • Authentication uses a personal access token. GitHub App installation tokens are in progress on a separate branch.
  • The planner sees the issue text and the files it names; it does not yet search the repository tree, and a failed verification fails the task instead of feeding the output back for another attempt.
  • Model-generated edits are limited to three whole files per task.
  • Verification runs inside the backend container under the command allowlist; there is no per-task sandbox beyond the workspace guard.
  • One process hosts both the API and the worker; splitting them is straightforward because the queue already lives in MySQL.

License

MIT, see LICENSE.

About

AI issue-to-PR agent backend: GitHub webhook trigger, safety gates, durable MySQL task queue, guarded repo tools, adapter-driven test verification, and pull requests for human review. Java 21, Spring Boot.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages