The follow-up repository for the O'Reilly live course AI Code Review in Production. The course ends with an AI reviewer running as a GitHub Action from one workflow file and one secret; this repository is the same TaskTracker app reviewed by a self-hosted fork of PR-Agent running as a GitHub App, with custom review logic and slash commands. It is what you build when you outgrow the Action: your own model, your own data boundary, your own rules engine.
The demo/add-search branch carries the same feature pull request with intentionally planted review findings, so the two reviewers can be compared on identical code.
A clean, full-stack app (FastAPI backend + React/TypeScript frontend, tests, CI) paired with a GitHub App webhook server running a fork of PR-Agent with custom code-standard enforcement. The bot auto-reviews pull requests and responds to slash commands like /check_standards, which validates code against your team's conventions without leaving the PR.
For your team: a template for shipping your own AI code reviewer with fine-grained control: set your model, tune the prompts, deploy once, and let it scale across your repos.
- Kanban board: organize work in
todo,in_progress,donecolumns with one-click status moves. - Projects & rich tasks: title, description, priority, assignee, due date per task.
- JWT auth: secure backend, password hashed with bcrypt.
- Typed end-to-end: Pydantic v2 models mirrored by TypeScript; full OpenAPI/Swagger docs.
- Production-shaped stack: service layer, dependency injection, Docker, GitHub Actions CI.
| Layer | Technologies |
|---|---|
| Frontend | React 18, TypeScript, Vite, React Router, CSS Modules, Vitest + Testing Library |
| Backend | Python 3.12, FastAPI, SQLAlchemy 2.x, Pydantic v2, JWT (python-jose), bcrypt, pytest |
| Data | SQLite (default); Postgres-ready via DATABASE_URL |
| Ops | Docker, docker-compose, GitHub Actions |
This repo's self-hosted setup runs three components:
-
GitHub App: registers your fork of PR-Agent as a GitHub App (permissions: read contents, write pull requests / issues). The app receives webhooks for PR events and slash commands via the webhook server.
-
Webhook server: a Render-hosted (or self-hosted) service running PR-Agent's
github_apptarget, listening onPOST /api/v1/github_webhooks. On PR open/reopen, it auto-runs/describeand/review; on PR comments, it dispatches slash commands. -
Custom
/check_standardscommand: a hand-written tool and tuned prompt in the fork that catches team conventions (hardcoded secrets, camelCase naming, bare exceptions, missing tests). Triggers on demand via/check_standardsor automatically on every new PR, depending on yourconfiguration.tomlsettings.
The flow: you push code, the PR opens, the webhook fires, the bot clones the repo, runs your configured tools (/describe, /review, /check_standards), and posts a single GitHub comment with findings. Each push triggers an incremental review. No polling, no external services scanning your code; it all runs on your server.
Trade-off vs. the marketplace Action: you own the infrastructure, control the model and cost, and can add custom rules. You also run a server (a free-tier Render instance is enough to try it, about USD 7 a month for production). The Action is instant but less flexible.
docker-compose up --build- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- Swagger UI: http://localhost:8000/docs
- Health check: http://localhost:8000/health
Demo account (auto-seeded):
- Email:
demo@tasktracker.dev - Password:
change-me
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
uvicorn app.main:app --reloadLoad demo data (optional):
python -m app.seedcd frontend
npm install
npm run devExpects the API at http://localhost:8000 by default. Override with VITE_API_BASE_URL.
The demo/add-search branch carries a feature PR with intentionally planted review-worthy code:
- Backend: hardcoded secret-like token, O(n) in-memory search instead of a DB query, raw-SQL string interpolation, bare
except:clause, missing type hints, no tests. - Frontend:
anytypes, missing React list keys, unbound search keystroke (no debounce), left-inconsole.log,dangerouslySetInnerHTML(XSS risk).
These issues are not marked or commented. When the reviewer analyzes the PR, it surfaces these findings in realistic PR comments, showing exactly what a self-hosted setup can catch. The /check_standards command is tuned to spot conventions like hardcoded secrets and bare exceptions.
Start here after the course. To run the self-hosted bot on your own repo:
- Fork the-pr-agent/pr-agent.
- Add your custom tools in
pr_agent/tools/(copy the/check_standardsexample). - Update the registration in
pr_agent/agent/pr_agent.py(add your tool tocommand2class). - Build the
github_appDocker target and push to a registry (GHCR, Docker Hub, etc.). - Register a GitHub App in your org's settings, subscribe to PR and issue-comment events.
- Deploy the webhook server (Render, AWS Lambda + webhook bridge, your own server, etc.).
- Set automatic triggers in
pr_agent/settings/configuration.tomlunder[github_app]: define which commands run on PR open, push, etc.
The bot will then auto-review every PR in that repo. Customize the prompts in pr_agent/settings/*.toml to match your team's style guide.
- Repo tour: the fork layout:
pr_agent/tools/,pr_agent/settings/*.toml(prompts),pr_agent/servers/github_app.py(webhook handler). - Build the image:
docker build -f docker/Dockerfile --target=github_app …. - Register a GitHub App: create the app in Settings, Developer settings, GitHub Apps, get the App ID and private key.
- Deploy to Render: push the image to GHCR, deploy two Render Starter services (primary + backup), wire the webhook URL.
- Add
/check_standards: the custom tool + prompt are already in a branch; show the diff, redeploy. - Trigger it: open the PR on
demo/add-search, watch the bot auto-review; comment/check_standardsand see the custom tool reply; push a commit and see incremental review fire.
tasktracker-selfhosted/
├── README.md
├── docker-compose.yml
├── .github/workflows/ci.yml
├── backend/
│ ├── requirements.txt
│ ├── .env.example
│ ├── Dockerfile
│ └── app/
│ ├── main.py, config.py, database.py, logging_config.py, seed.py
│ ├── api/routers/ (health, auth, users, projects, tasks)
│ ├── core/security.py
│ ├── models/ (user, project, task ORM models)
│ ├── schemas/ (Pydantic request/response models)
│ ├── services/ (business logic layer)
│ └── tests/ (pytest: auth, projects, tasks, conftest fixtures)
└── frontend/
├── package.json, tsconfig.json, vite.config.ts, .eslintrc.cjs
├── Dockerfile
└── src/
├── main.tsx, App.tsx, types.ts
├── api/client.ts (typed fetch + JWT bearer auth)
├── components/ (Header, Login, TaskBoard, TaskColumn, TaskCard, TaskForm)
├── pages/ (LoginPage, BoardPage)
├── hooks/ (useAuth, useProjects, useTasks)
├── styles/ (CSS modules + global)
└── __tests__/ (Vitest component tests)
All endpoints versioned at /api/v1 (except /health). Endpoints marked Auth require Authorization: Bearer <token>.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/health |
Liveness check. | |
POST |
/api/v1/auth/register |
Register user. | |
POST |
/api/v1/auth/login |
Log in, get JWT. | |
GET |
/api/v1/users/me |
✓ | Current user. |
GET |
/api/v1/projects |
✓ | List your projects. |
POST |
/api/v1/projects |
✓ | Create project. |
GET |
/api/v1/projects/{id} |
✓ | Get project. |
PATCH |
/api/v1/projects/{id} |
✓ | Update project. |
DELETE |
/api/v1/projects/{id} |
✓ | Delete project. |
GET |
/api/v1/tasks |
✓ | List tasks; filter by project, status, assignee; paginate. |
POST |
/api/v1/tasks |
✓ | Create task. |
GET |
/api/v1/tasks/{id} |
✓ | Get task. |
PATCH |
/api/v1/tasks/{id} |
✓ | Update task. |
DELETE |
/api/v1/tasks/{id} |
✓ | Delete task. |
POST |
/api/v1/tasks/{id}/move |
✓ | Move task to new status. |
Full interactive specification at /docs (Swagger UI) when the backend runs.
cd backend
ruff check . # lint
pytest # tests (40+ test functions)cd frontend
npm run lint # eslint
npm run build # tsc + vite (type-check + bundle)
npm run test # vitest (component tests)All suites run on every push and PR via GitHub Actions (.github/workflows/ci.yml).
Backend config is environment-driven via pydantic-settings. Copy and edit:
cp backend/.env.example backend/.envKey variables:
| Variable | Default | Purpose |
|---|---|---|
APP_NAME |
TaskTracker |
App name in OpenAPI. |
ENVIRONMENT |
development |
One of development / staging / production. |
LOG_LEVEL |
INFO |
Logging level. |
DATABASE_URL |
sqlite:///./data/app.db |
SQLAlchemy URL; use a Postgres URL in production. |
SECRET_KEY |
change-me |
JWT signing key. Set a strong value outside local dev. |
ACCESS_TOKEN_EXPIRE_MINUTES |
60 |
Token lifetime. |
BACKEND_CORS_ORIGINS |
http://localhost:5173,http://127.0.0.1:5173 |
Comma-separated allowed origins. |
Frontend reads VITE_API_BASE_URL at build time (default http://localhost:8000). Vite inlines VITE_* into the bundle, so override it at build time:
VITE_API_BASE_URL=https://api.example.com npm run buildSecrets: .env.example files hold placeholders only. Never commit real secrets; always override SECRET_KEY and seed credentials outside development.
- O'Reilly live course: AI Code Review in Production; the marketplace-Action repository the course builds on: https://github.com/SerhiiYakovenko/tasktracker
- PR-Agent: https://github.com/the-pr-agent/pr-agent
- PR-Agent docs: https://docs.pr-agent.ai
Released under the MIT License.
Follow-up material for the O'Reilly live course AI Code Review in Production (November 2026). This repository was first shown as the self-hosted half of a hands-on workshop at TechLeadConf 2026. Read the code, fork it, self-host the bot, and extend it for your team.