A small, self-hosted, webhook-driven code reviewer. When a pull request is opened or updated, TurtleCode runs a local LLM over the diff and posts a review comment back to the PR.
It's a standard queue + worker service: an HTTP endpoint receives GitHub webhooks (the producer), a Redis-backed queue buffers work, and workers consume jobs and call a local Ollama model. Built to run at small scale but to production standards — durable queue, retries, idempotency, timeouts, and guardrails around a weak model.
GitHub ──webhook──▶ web (producer) worker (consumer)
verify HMAC reserve job
filter PR events skip if done / superseded
enqueue, return 202 fetch diff ─▶ chunk ─▶ LLM review (guarded)
│ upsert one comment on the PR
└────▶ Redis + BullMQ ────┘
(persistent, retries, DLQ)
The web tier returns 202 immediately and never blocks on the model. All slow or
failure-prone work happens in the worker, backed by a durable queue with retries and a
dead-letter queue. See ARCHITECTURE.md for the full design.
Stack: Node.js · Express · BullMQ/Redis · Ollama (codellama) · Octokit · Zod · Docker/k3s.
- HMAC-verified GitHub webhook ingress (
pull_requestevents) - Durable, at-least-once queue (Redis + BullMQ) with exponential backoff and a dead-letter queue
- Idempotent output: one bot comment per PR, upserted by head SHA — retries never duplicate
- Supersession: stale commits are dropped instead of posting outdated reviews
- Guardrails for a small model: JSON-schema validation, anti-hallucination line check, confidence gate, per-call timeouts with real cancellation
- Horizontally scalable stateless web and worker roles
Copy .env.example to .env and fill in:
| Variable | Default | Description |
|---|---|---|
ROLE |
all |
web, worker, or all (both in one process) |
PORT |
3000 |
web listen port |
REDIS_URL |
redis://localhost:6379 |
Redis connection |
OLLAMA_BASE_URL |
http://localhost:11434 |
Ollama API |
LLM_MODEL |
codellama |
model name |
GITHUB_WEBHOOK_SECRET |
— | HMAC secret; must match the GitHub webhook |
GITHUB_TOKEN |
— | PAT with Pull requests: Read and write |
WORKER_CONCURRENCY |
2 |
jobs per worker (global LLM concurrency = workers × this) |
LLM_TIMEOUT_MS |
30000 |
per model-call timeout |
MAX_ATTEMPTS |
5 |
retries before dead-lettering |
Keep comments on their own lines in
.env— inlineKEY=val # commentcan be ingested verbatim by tools likekubectl create secret --from-env-file.
npm install
cp .env.example .env # then fill in GITHUB_* values
docker run -d -p 6379:6379 redis:7 # queue
ollama serve # model host
npm start # ROLE=all: web + worker in one process
# or run the roles separately, e.g. to scale workers:
npm run web
WORKER_CONCURRENCY=4 npm run workerExpose the web port publicly (e.g. via a Cloudflare Tunnel or ngrok) and point a GitHub
webhook at https://<your-host>/webhook.
Repo → Settings → Webhooks → Add webhook:
- Payload URL:
https://<your-host>/webhook - Content type:
application/json - Secret: same value as
GITHUB_WEBHOOK_SECRET - Events: Let me select individual events → Pull requests
Manifests are in k8s/:
turtlecode.yaml— Redis, Ollama, and the web + worker Deployments/Servicesturtlecode-tunnel.yaml— an optional Cloudflare Tunnel connector
# build & push the image to your registry
docker build -t <registry>/turtlecode:<tag> .
docker push <registry>/turtlecode:<tag>
kubectl create namespace turtlecode
kubectl -n turtlecode create secret generic turtlecode-secrets \
--from-literal=GITHUB_WEBHOOK_SECRET=... \
--from-literal=GITHUB_TOKEN=...
kubectl apply -f k8s/turtlecode.yaml
# one-time: pull the model onto the Ollama volume
kubectl -n turtlecode exec deploy/ollama -- ollama pull codellamaindex.js entry point / bootstrap (role: web | worker | all)
src/
config.js env → config
logger.js structured JSON logs
queue.js Redis + BullMQ queues
signature.js HMAC webhook verification
web.js Express producer (/webhook, /healthz)
worker.js BullMQ consumer pipeline
chunk.js diff → per-hunk chunks
reviewer.js Ollama call + schema/guardrails
github.js Octokit: fetch diff, upsert comment
k8s/ Kubernetes manifests
test/ node:test suites
npm test # node --testISC