Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

158 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Themis

Themis is a self-hosted GitHub PR review bot that runs on your own Codex or Claude Max subscription. It reviews pull requests with inline findings and a structured summary (verdict, scoring table, severity-ordered sections), answers questions in review threads and PR conversation, can re-review pushed commits as a scoped delta once a review exists (opt-in), and takes its review doctrine from your own repository, under .themis/.

How it works

A GitHub App webhook delivers PR and comment events to Themis. Each event becomes a job on an in-memory queue, processed one at a time by default (THEMIS_CONCURRENCY runs up to 8 in parallel; the practical limit is your engine subscription quota). The worker shallow-clones the PR head, runs the configured engine (codex exec, or claude -p β€” natively or in API mode for GLM, Kimi, and OpenRouter) against your repo's review doctrine, and posts findings and a summary back to GitHub as the App. Issues and PRs the description references (Fixes #12; same owner only, and only public sibling repos) are fetched and handed to the reviewer as context. One image runs as an isolated controller and agent; there is still no database, Redis, or message broker.

Prerequisites

  • Docker with the Compose plugin (Docker Desktop, or Docker Engine + docker compose)
  • An OpenAI account with Codex access, or a Claude Max subscription (pick your engine): the matching CLI installed (npm install -g @openai/codex or npm install -g @anthropic-ai/claude-code, Node 22+) and codex login or claude setup-token working on your machine. Claude Pro is not supported because Themis defaults to Opus. The glm engine needs no local CLI login: just a GLM_API_KEY (Z.ai GLM Coding Plan) in .env. The kimi and openrouter engines are the same: just KIMI_API_KEY (Moonshot platform, pay-as-you-go) or OPENROUTER_API_KEY (OpenRouter credits) in .env.
  • A GitHub account that can create a GitHub App (personal account or an org)

No clone or build needed: Themis ships as a prebuilt image, ghcr.io/zaimwa9/themis.

Choose a setup path

Path GitHub App Best for
GitHub Action none zero infrastructure: reviews on GitHub's runners
Quick start auto-created via manifest, random name trying Themis in minutes, throwaway deployments
Production setup created by you once, stable name long-term installs that survive redeployments
Headless mode created by you once teams with existing webhook infrastructure

The three server paths run the same prebuilt image; all four support every engine.

GitHub Action (zero infrastructure)

No server, tunnel, or GitHub App: a workflow runs the same engine + prompt pipeline on GitHub's runners and posts with the workflow's GITHUB_TOKEN. Copy examples/github-actions/themis-review.yml into .github/workflows/, add your engine credential as a repo secret (e.g. CLAUDE_CODE_OAUTH_TOKEN), done. New PRs get reviewed; @themis review and discussion replies work like in server mode. Trade-offs (no per-repo learnings, no fork-PR reviews, runner minutes) and the full input reference: docs/github-action.md.

Quick start (manifest bootstrap β€” testing, not long-term)

The bootstrap uses GitHub's App Manifest flow to create the App, generate its private key and webhook secret, install it on the requested repository, and write a ready-to-run deployment. There are no GitHub settings to copy. GitHub still asks the account owner to approve App creation and repository access.

Why not long-term? Only the App name: the manifest flow generates a random one and it can't be reserved in advance. The App itself is created once β€” the credentials in the generated .env stay valid across image upgrades, restarts, and machine moves, and the webhook URL re-registers itself at startup, so you never re-run the bootstrap for the same deployment. Re-running is only for a new App (fresh environment, key rotation, or a .env you no longer have). For a stable name and @mention, use the Production setup below.

Using a coding agent

Copy the prompt below into Claude Code, Codex, or any agent that can run shell commands. It handles the entire bootstrap autonomously:

Set up Themis for this repository using the automated GitHub App Manifest bootstrap:

  https://github.com/Zaimwa9/themis/blob/main/docs/bootstrap.md

  Use the Claude engine and the bundled ngrok tunnel.

  Do not manually create or configure a GitHub App. Run `python -m themis init` with
  `--engine claude --tunnel`.

  Handle everything autonomously. Only pause when GitHub requires my approval, or when
  I need to provide an ngrok auth token or complete Claude authentication.

  After setup, start the deployment, verify it works, and tell me the bot's @mention
  and how to trigger a review.

1. Log in to Codex

Using the Claude engine instead? Pass --engine claude to the bootstrap: it runs claude setup-token for you and writes the resulting long-lived token into CLAUDE_CODE_OAUTH_TOKEN in the generated .env (set that variable beforehand to skip the interactive flow). Using glm, kimi, or openrouter? No CLI login needed: pass --engine <name> to the bootstrap and put the provider key (GLM_API_KEY, KIMI_API_KEY, or OPENROUTER_API_KEY) in the generated .env. Details in Engines.

Install the CLI if you haven't already (Node 22+):

npm install -g @openai/codex

Mint a login chain dedicated to this deployment (a browser window opens):

CODEX_HOME="$PWD/themis-codex" codex login

This writes $PWD/themis-codex/auth.json, which the bootstrap seeds into the generated deployment with mode 0600. Do not reuse or mount your personal ~/.codex/auth.json: ChatGPT refresh tokens are single-use rotating, so two installs sharing one chain invalidate each other β€” whichever refreshes first kills the other, and reviews start failing with expired-credential errors. Once the container owns its own chain, codex refreshes it indefinitely with no further logins. (Running the bootstrap from a source checkout instead of Docker? Omit --codex-auth entirely β€” it runs codex login against a private scratch home for you.)

2. Bootstrap

For an instance with an existing public HTTPS URL:

mkdir themis-deploy
docker run --rm -it \
  --user "$(id -u):$(id -g)" \
  -p 127.0.0.1:8976:8976 \
  -v "$PWD/themis-deploy:/output" \
  -v "$PWD/themis-codex:/host-codex:ro" \
  ghcr.io/zaimwa9/themis:latest \
  python -m themis init \
  --repo OWNER/REPO \
  --public-url https://themis.example.com \
  --output /output \
  --codex-auth /host-codex/auth.json \
  --bind-host 0.0.0.0 \
  --no-browser

For a local machine without a public URL, export an ngrok token and replace --public-url ... with --tunnel:

export NGROK_AUTHTOKEN=<your-token>
# Add `-e NGROK_AUTHTOKEN` to docker run, then pass `--tunnel` to themis init.

If the target is owned by an organization, also pass --organization OWNER. The App must be created under that organization because the generated App is private. The command prints a localhost URL. Open it, approve the pre-filled App, and select OWNER/REPO on the installation screen. The private key never leaves the bootstrap process and the generated .env. The success screen and terminal show the bot's generated @mention; it is also saved in themis-info.json for later reference.

The same command can run from a source checkout without Docker:

uv run python -m themis init \
  --repo OWNER/REPO \
  --public-url https://themis.example.com \
  --output ./themis-deploy

Detailed options, the tunnel command, and recovery are in docs/bootstrap.md.

3. Run and verify

cd themis-deploy
docker compose up -d

Use docker compose --profile tunnel up -d when the bootstrap used --tunnel. Themis discovers the tunnel URL and updates the App webhook automatically.

curl localhost:8000/healthz
# {"status":"ok"}

Check docker compose logs themis for the startup line reporting the resolved App slug. Open a test PR against a repo the App is installed on: expect a πŸ‘€ reaction on the trigger, then a πŸš€ reaction on the PR once the review starts, then the review itself.

Production setup (manual GitHub App)

Create the App yourself, once: it gets a stable name and @mention, and any number of deployments can point at it without ever re-running a bootstrap.

1. Create the GitHub App

Under Settings > Developer settings > GitHub Apps > New GitHub App, on the organization that owns the target repos or on your personal account:

Setting Value
GitHub App name your choice, must be unique on GitHub; its slug becomes the bot's @mention
Homepage URL any URL; https://github.com/Zaimwa9/themis works
Webhook URL https://HOST/webhook; any placeholder works if you set THEMIS_PUBLIC_URL later, Themis re-registers it at startup
Webhook secret a long random string, goes in THEMIS_GH_WEBHOOK_SECRET
Checks permission Read-only
Contents permission Read and write (write publishes learnings digest PRs)
Issues permission Read and write
Pull requests permission Read and write
Commit statuses permission Read-only
Events pull_request, issue_comment, pull_request_review_comment

Actions permission is not required. Existing Apps must be updated with the Checks and Commit statuses permissions before Themis can include CI context in reviews, and with Contents write before it can open learnings digest PRs; each installation must then accept the permission upgrade GitHub sends it.

Then generate a private key (App settings > Private keys) and install the App on the target repositories (App settings > Install App).

2. Configure and deploy

Grab the Compose file and point it at the published image:

mkdir themis-deploy && cd themis-deploy
curl -fsSLO https://raw.githubusercontent.com/Zaimwa9/themis/main/docker-compose.yml
# edit: replace the two `build: .` lines with `image: ghcr.io/zaimwa9/themis:latest`

Create .env next to it (.env.example documents every key):

THEMIS_GH_APP_CLIENT_ID=<App client id>
THEMIS_GH_APP_PRIVATE_KEY=<PEM, or base64 of it>
THEMIS_GH_WEBHOOK_SECRET=<the webhook secret>
THEMIS_AGENT_TOKEN=<any long random string>
THEMIS_PUBLIC_URL=https://your-host    # optional: webhook self-registration
THEMIS_ENGINE=codex                    # or claude / glm / kimi / openrouter
CLAUDE_CODE_OAUTH_TOKEN=<token>        # claude engine only
GLM_API_KEY=<key>                      # glm engine only
KIMI_API_KEY=<key>                     # kimi engine only
OPENROUTER_API_KEY=<key>               # openrouter engine only
docker compose up -d

For the codex engine, mint a dedicated chain and seed the auth volume once the agent is up β€” never your personal ~/.codex/auth.json (see the chain warning above). The pipe runs as the container's unprivileged themis user, so ownership and 0600 mode come out right (docker compose cp would leave the file root-owned and unreadable to the agent):

scratch=$(mktemp -d)
CODEX_HOME="$scratch" codex login
docker compose exec -T agent sh -c 'umask 077; cat > /data/codex/auth.json' \
  < "$scratch/auth.json"
rm -rf "$scratch"

PaaS deployment, upgrades, and the full env reference: docs/server-deploy.md and docs/configuration.md.

3. Verify

Same checks as the quick start: curl localhost:8000/healthz, look for the App slug in docker compose logs themis, open a test PR.

Headless mode (bring your own webhook)

Already have webhook infrastructure? Set THEMIS_WEBHOOK_ENABLED=false to remove Themis's inbound webhook route and drive it from your own handler through two authenticated HTTP routes, POST /api/review and POST /api/discuss. The GitHub App still has to exist and be installed, created as in the Production setup. Contracts and examples: docs/headless.md.

Customize reviews

Copy the starter kit into the target repo. This needs a temporary shallow checkout of Themis (the deployment itself does not):

starter="$(mktemp -d)"
git clone --depth 1 https://github.com/Zaimwa9/themis.git "$starter"
cp -r "$starter/examples/themis" .themis
  • .themis/review.md: the review doctrine, philosophy, severity calibration, a map of your codebase, house rules. Edit this; it is read straight from the PR branch on every review.
  • .themis/config.yaml: behavior knobs, every key optional.

Every review uses the full-dress presentation by default: a four-row numeric /5 scorecard, collapsible walkthrough and verification/assumptions blocks, product take, and an italic PR-specific sign-off. Empty categories carry a short empty-state message; only explicit off configuration suppresses one. If there is no .themis/review.md, Themis separately falls back to a packaged default doctrine; committing a doctrine replaces only that judgment guidance.

How the doctrine is consumed and how to write one that works: docs/doctrine.md. This repo reviews itself with its own .themis/review.md.

Themis also learns as you use it: correct it in a PR thread (or say @themis remember <rule>) and, if you're a repo owner/member/collaborator, it saves the convention and applies it to future reviews β€” landing it in .themis/learnings.jsonl through a digest PR you review like any other. See docs/learnings.md.

Key Default Meaning
engine instance THEMIS_ENGINE codex, claude, glm, kimi, or openrouter, overrides the instance's default engine for this repo
web_access false toggles engine web tooling (WebFetch/WebSearch); glm/kimi/openrouter behave like claude here, and Claude's Bash may still egress unless the deployment enforces an external network policy β€” this caveat applies to all claude-harness engines
model.name engine default engine default: gpt-5.4 (codex), claude-opus-4-6[1m] (claude), glm-5.2 (glm), kimi-k3 (kimi), openrouter/auto (openrouter β€” any OpenRouter slug can be set; only Anthropic first-party models are guaranteed by OpenRouter's Claude Code integration)
model.reasoning_effort high low | medium | high (codex only)
limits.timeout_seconds 1200 per agent attempt
limits.max_attempts 2 attempts before posting a failure comment
limits.clone_depth 50 shallow clone depth
triggers.auto_review true false = mention-only, no auto-review when a PR opens or is marked ready for review
triggers.delta_review false opt-in: true makes pushes to an already-reviewed PR trigger a scoped re-review of just the new commits, verifying open findings along the way β€” each non-coalesced push may then cost an engine run; see docs/configuration.md
triggers.skip_titles [] case-insensitive wildcard patterns matched against the PR title (e.g. 'ci: *', '*[skip review]*'); a match skips the auto-review with a comment naming the rule, mentions still work β€” see docs/configuration.md
learnings.enabled true false disables capturing, injecting, and digesting learnings for this repo
learnings.digest_threshold 10 pending learnings that trigger the digest PR (minimum 1)
review.modules.<name> full-dress profile always | auto | off per optional review section; all six presentation categories default to enabled and only off suppresses them; see docs/configuration.md
review.modules.big_picture auto the Big picture: architecture/maintainability note; auto shows it only when the change provides concrete structural evidence (no empty-state filler), always pins it, off suppresses the note while structural defects still surface as findings
agent.context false agent natively discovers CLAUDE.md/AGENTS.md, always resolved from the PR base revision so a PR can't steer its own review; see docs/configuration.md
agent.skills false agent uses .claude/skills packages, same base-revision rule β€” natively on claude/glm/kimi/openrouter, via a synthesized index (skills bridge) on codex

Can't commit .themis/config.yaml to the target repo (yet)? Set THEMIS_DEFAULT_REPO_CONFIG on the controller to the same yaml (raw or base64) and it applies to every repo that has no .themis/config.yaml of its own; a committed file always wins. See docs/configuration.md.

Talk to the bot in a PR: @<app-slug> review re-reviews on demand, @<app-slug> review <focus> steers the review toward a given area (the focus text is honored only from repo owners, org members, and collaborators β€” anyone else gets a plain review), @<app-slug> <question> asks a question, and replies inside a thread the bot already posted in are answered automatically, no mention needed.

A standing finding doesn't hold the verdict hostage forever: resolve the bot's thread on GitHub (the PR author or someone with write access can), or reply in it accepting the trade-off (repo owners, org members, and collaborators only), and the next review moves that finding to a βš–οΈ Acknowledged one-liner β€” naming who resolved or accepted it β€” and the verdict is computed from what's still open. If the code the finding covers changes materially later, it can come back as open.

Engines

Themis runs reviews through an agent CLI, using your Codex, Claude Max, or GLM Coding Plan subscription β€” or pay-per-token Moonshot/OpenRouter credentials:

Engine Auth Setup
codex (default) auth.json volume (CODEX_HOME) bootstrap mints a dedicated login chain (or seeds one you pass via --codex-auth); never share a chain between installs
claude one env var bootstrap runs claude setup-token and writes CLAUDE_CODE_OAUTH_TOKEN into .env
glm one env var set GLM_API_KEY in .env (Z.ai GLM Coding Plan key); reviews run through the claude CLI against Z.ai's Anthropic-compatible endpoint
kimi one env var set KIMI_API_KEY in .env (Moonshot pay-as-you-go platform key β€” not a Kimi Code subscription, whose terms exclude non-interactive use); reviews run through the claude CLI against Moonshot's Anthropic-compatible endpoint
openrouter one env var set OPENROUTER_API_KEY in .env (prepaid credits); reviews run through the claude CLI against OpenRouter's Anthropic-protocol gateway β€” model.name accepts any OpenRouter slug, though OpenRouter only guarantees Anthropic first-party models with Claude Code

Pick the instance default with THEMIS_ENGINE in .env. A repo can override it in .themis/config.yaml with engine: set to any of them; if that engine has no credentials on the instance, Themis posts a comment saying so instead of failing silently. The claude, glm, kimi, and openrouter paths need no volume: key in .env, done.

Troubleshooting

Symptom Fix
Crashes at startup naming an env var Set that variable; Themis fails fast on missing or invalid required config.
Crashes at startup on a GET /app call Wrong THEMIS_GH_APP_CLIENT_ID or malformed THEMIS_GH_APP_PRIVATE_KEY.
Codex sandbox errors Set THEMIS_CODEX_SANDBOX=danger-full-access; the container is the sandbox boundary on runtimes without Landlock.
PR comment says the usage limit was reached The subscription of whichever engine ran the job has hit its usage window. Mention the bot again once it resets.
Auth that worked starts failing months later Run codex login locally, then refresh the persistent agent credential using the command in Automated setup: Refreshing Codex authentication.
Review comment says engine credentials missing Set CLAUDE_CODE_OAUTH_TOKEN (claude), GLM_API_KEY (glm), KIMI_API_KEY (kimi), OPENROUTER_API_KEY (openrouter), or seed the codex auth volume (codex), or change THEMIS_ENGINE / the repo's engine: key.
Webhook deliveries show 401 in the App's settings THEMIS_GH_WEBHOOK_SECRET doesn't match the App's webhook secret.
Where are the logs docker compose logs -f themis
A job queued right before a restart never ran The in-memory queue doesn't survive restarts; mention the bot again to re-trigger.
Opened a PR, nothing happened Check in order: PR is a draft (auto-review skips drafts until marked ready; an explicit review mention or /api/review call still runs, and questions are always answered); PR author is a bot account (ignored); auto_review: false in .themis/config.yaml; the GitHub App isn't installed on that repo; webhook deliveries are failing (App settings > Advanced > Recent Deliveries).

Documentation

Developing

Working on Themis itself needs uv and Python 3.12, no Docker:

uv sync --locked        # install deps into .venv
uv run pytest           # run the test suite
uv run ruff check .     # lint
uv run python -m themis # run the server locally (reads THEMIS_* from the environment)

CI runs the same pytest and ruff commands on every push and pull request.

License

MIT. See LICENSE.

About

Open source & self-hostable review agent

Resources

Security policy

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages