DocsGuidesGithub app

GitHub App

The Quell GitHub App watches every pull request across your organisation. You install it once and every repo gets automatic guard-clause reviews with no YAML files to add or maintain.

If you only need coverage for one or two repos, the GitHub Actions guide is simpler — it needs no server. Use the App when you want zero-config coverage across many repos or an org-wide policy.


How it works

GitHub sends pull_request webhook
    ↓
HMAC-SHA256 signature verification
    ↓
Exchange App JWT → installation access token (1-hour TTL, scoped to one repo)
    ↓
Fetch changed .py files via GitHub Contents API  (no clone)
    ↓
CodeGuardReader scans each file  (pure AST, offline)
    ↓
Post / update a single PR comment  (idempotent — never spams)

No repository is cloned. No code is transmitted anywhere. The scanner is purely AST-based and runs inside your own infrastructure.


Setup

Create the GitHub App

Go to GitHub → Settings → Developer settings → GitHub Apps → New GitHub App (for an org: Org Settings → Developer settings → GitHub Apps).

Fill in:

FieldValue
App nameQuell (or quell-yourorg)
Homepage URLhttps://quell.buildsbyshashank.tech
Webhook URLhttps://<your-server>/github/webhook — set this after you deploy in Step 3
Webhook secretGenerate a random string, save it — you'll need it in Step 2

Under Repository permissions:

  • Contents → Read-only
  • Pull requests → Read & write

Under Subscribe to events:

  • Tick Pull request

Click Create GitHub App. Note the App ID shown on the settings page.

Download the private key and note your credentials

On the App settings page, scroll to Private keysGenerate a private key. A .pem file downloads automatically. Keep it safe — this is the only copy.

You now have four values you'll need:

VariableWhere
GITHUB_APP_IDThe number on the App settings page (e.g. 123456)
GITHUB_APP_PRIVATE_KEYThe full contents of the .pem file
GITHUB_WEBHOOK_SECRETThe secret you entered in Step 1
PORTPort for the server (default 8080)

Format the private key for an environment variable — the PEM file has real newlines; most hosting platforms expect them escaped as \n:

# macOS / Linux
cat your-app.pem | awk 'NF {printf "%s\\n", $0}' | pbcopy
# Windows PowerShell
(Get-Content your-app.pem -Raw).Replace("`r`n","\n").Replace("`n","\n") | Set-Clipboard

Paste the result as the value of GITHUB_APP_PRIVATE_KEY.

Deploy the webhook server

The server is a standard FastAPI app at quell/github/app.py inside the quelltest_lib repository. Deploy it wherever you host Python services.

Render (free tier):

  1. Fork quelltest_lib to your GitHub account.
  2. render.com → New → Web Service → connect the fork.
  3. Set:
    • Build command: pip install quelltest fastapi uvicorn PyJWT cryptography
    • Start command: uvicorn quell.github.app:app --host 0.0.0.0 --port $PORT
  4. Add the three environment variables (GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, GITHUB_WEBHOOK_SECRET).
  5. Deploy. Copy the https://your-app.onrender.com URL — paste it into the App's Webhook URL field.

Docker (self-hosted):

FROM python:3.11-slim
RUN pip install quelltest fastapi uvicorn PyJWT cryptography
CMD ["uvicorn", "quell.github.app:app", "--host", "0.0.0.0", "--port", "8080"]
docker build -t quell-app .
docker run -p 8080:8080 \
  -e GITHUB_APP_ID=123456 \
  -e GITHUB_APP_PRIVATE_KEY="$(cat your-app.pem | awk 'NF {printf "%s\\n", $0}')" \
  -e GITHUB_WEBHOOK_SECRET=your-secret \
  quell-app

Local testing with ngrok:

pip install quelltest fastapi uvicorn PyJWT cryptography
export GITHUB_APP_ID=123456
export GITHUB_APP_PRIVATE_KEY="$(cat your-app.pem | awk 'NF {printf "%s\\n", $0}')"
export GITHUB_WEBHOOK_SECRET=your-secret
uvicorn quell.github.app:app --host 0.0.0.0 --port 8080
# in another terminal:
ngrok http 8080

Update the App's Webhook URL to the https://...ngrok-free.app URL while testing.

Railway / Fly.io:

# Railway
railway init && railway up
railway variables set GITHUB_APP_ID=... GITHUB_APP_PRIVATE_KEY=... GITHUB_WEBHOOK_SECRET=...

# Fly.io
fly launch --name quell-app
fly secrets set GITHUB_APP_ID=... GITHUB_APP_PRIVATE_KEY=... GITHUB_WEBHOOK_SECRET=...
fly deploy

Verify the webhook

In the GitHub App settings → AdvancedRecent Deliveries, you should see a ping event with a 200 response after saving the Webhook URL.

ResponseCause
200Working correctly
403Webhook secret mismatch — check GITHUB_WEBHOOK_SECRET
502 / 503Server not running or wrong URL
500Private key formatting error — check newlines in GITHUB_APP_PRIVATE_KEY

Install the App on repositories

GitHub App settings → Install App → choose All repositories or select specific ones.

Open (or re-open) any pull request in an installed repo. Within a few seconds Quell posts a comment listing every untested guard clause in the changed files.


PR comment format

🟡 Quell — Guard Clause Scan

3 untested guard clauses found in changed files — 60% covered (3/5)

| File            | Function        | Guard                    | Type      |
|-----------------|-----------------|--------------------------|-----------|
| payments.py:32  | process_payment | boundary condition —...  | boundary  |
| sessions.py:18  | create_session  | must not be None — ...   | not_null  |
| auth.py:44      | require_auth    | auth/permission check... | auth      |

Fix locally: quell scan src/ --fix

---
Quell v0.9.8 — rule-based, no code sent to any server

The comment is idempotent — Quell updates the same comment on every push to the PR branch rather than posting a new one.


Environment variables reference

VariableRequiredDescription
GITHUB_APP_IDYesNumeric App ID from the App settings page
GITHUB_APP_PRIVATE_KEYYesPEM private key contents (\n-escaped for env vars)
GITHUB_WEBHOOK_SECRETYesSecret configured in the App's webhook settings
PORTNoPort to listen on. Defaults to 8080.

Troubleshooting

Webhook delivers 403 The GITHUB_WEBHOOK_SECRET in your environment doesn't match the secret entered in the App settings. Regenerate it in both places.

Webhook delivers 500 (private key error) The PEM newlines weren't escaped correctly. Every line of the PEM file must be joined with a literal \n (backslash + n), not a real newline character. Re-run the awk or PowerShell command from Step 2 and update the env var.

App installed but no comment appears

  • Check App settings → Advanced → Recent Deliveries for errors.
  • Confirm Pull request is checked under Subscribe to events.
  • The App only processes opened, synchronize, and reopened actions. If the PR was already open before the App was installed, close and re-open it.
  • The App only scans .py files that are added or modified in the diff. A PR that only touches non-Python files produces no comment.

Comment posted with "No guard clauses found" The changed files have no if/raise, try/except/raise, assert, or standalone raise patterns — or every guard clause already has a test. This is correct behaviour.