Configuration
Configuration is read from environment variables (see .env.example). Short
names (AI_*, REVIEW_*, WEBHOOK_*, ...) are explicit aliases; every other
thrillhousebot.* key is settable through the standard Quarkus env-var mapping
— uppercase with ./- replaced by _ (e.g. thrillhousebot.review.ignored-files
→ THRILLHOUSEBOT_REVIEW_IGNORED_FILES). The AI variables are the ones you
will change per provider:
| Variable | Purpose | Default |
|---|---|---|
AI_API_KEY | API key for the AI provider | (required) |
AI_BASE_URL | OpenAI-compatible base URL | https://api.deepseek.com/v1 |
AI_MODEL | Chat model name | deepseek-chat |
AI_PROVIDER | Provider label for telemetry (gen_ai.provider.name); derived from AI_BASE_URL when unset | (derived) |
AI_TIMEOUT | Per-request timeout | 300s |
AI_REASONING_ENABLED | Send a reasoning hint to reasoning-capable models; when false no reasoning parameter is sent and the provider default applies | false |
AI_REASONING_EFFORT | Effort sent while enabled: none/low/medium/high (none explicitly asks the model not to reason); reasoning tokens are billed as output tokens | low |
GITHUB_APP_ID | GitHub App ID | (required) |
GITHUB_PRIVATE_KEY | GitHub App private key (PEM) | (required) |
GITHUB_WEBHOOK_SECRET | Webhook HMAC secret | (required) |
GITHUB_BOT_LOGINS | Comma-separated bot account login(s) the bot skips to avoid replying to itself; override when deployed under a different App slug (<app-slug>[bot]) | thrillhousebot[bot],thrillhouse-bot[bot] |
WEBHOOK_DEDUP_TTL | Webhook deduplication time-to-live for GitHub redeliveries | 24h |
THRILLHOUSEBOT_REVIEW_MANUAL_TRIGGER_ALLOWED_LOGINS | Comma-separated allowlist of logins permitted to trigger manual /review without repo access | (empty) |
MANUAL_TRIGGER_AUTH_TIMEOUT | Upper bound on the manual-trigger write-access check on the webhook ACK thread; fails closed (denies) if GitHub is slower | 5s |
ACK_REACTION_TIMEOUT | Upper bound on the 👀 command-ack reaction on the webhook ACK thread; the wait is abandoned (reaction may land late) if GitHub is slower | 3s |
AUTO_REVIEW_MIN_INTERVAL | Minimum interval between automatic reviews of the same PR — pushes within the window are skipped silently, even on a new head SHA (in-memory, per replica). A manual /review always bypasses; unset or 0 reviews every push | 0 (disabled) |
WEBHOOK_SKIP_DRAFTS | Skip auto-review while a PR is a draft (reviewed once marked ready / on later pushes) | false |
WEBHOOK_REQUIRED_LABELS | Comma-separated labels; only auto-review PRs carrying at least one (case-insensitive) | (empty — no gate) |
WEBHOOK_EXCLUDED_LABELS | Comma-separated labels; skip auto-review of PRs carrying any (wins over required) | (empty) |
WEBHOOK_BASE_BRANCHES | Comma-separated globs; only auto-review PRs whose base branch matches one (e.g. main,release/*). Globs are gitignore-style: * does not cross /, so use ** to span slashes (** alone matches every branch) | (empty — all branches) |
WEBHOOK_IGNORED_BASE_BRANCHES | Comma-separated globs; skip auto-review of PRs whose base branch matches one (wins over allowlist; same */** rule — match nested branches with **, e.g. dependabot/**) | (empty) |
REVIEW_VERIFIER_ENABLED | Second, skeptical AI pass that re-checks each finding against the diff before posting, dropping or downgrading what it can't confirm (see AI call budget); fails open — a verifier error keeps the original findings | true |
REVIEW_CONVERSATIONAL_REPLIES_ENABLED | Answer @thrillhousebot mentions in PR threads (including finding replies) with an AI reply | true |
REVIEW_ADD_DOCS_ENABLED | Allow the on-demand /add-docs command to generate docstrings as committable suggestions | true |
REVIEW_DIAGRAM_ENABLED | Include an opt-in Mermaid control-flow diagram in the PR summary | false |
REVIEW_MAX_INPUT_TOKENS | Per-call input-token budget for review calls; large PRs are split into batches that each fit it. Bounded by the active model's input cap (see Per-model AI settings). 0 disables token budgeting | 48000 |
REVIEW_OUTPUT_BUFFER_TOKENS | Tokens reserved out of the input budget for the model's response | 8192 |
REVIEW_MAX_AI_CALLS | Cap on AI calls per review (batch calls plus the final summary call); files that still don't fit are reported by name as omitted | 6 |
REVIEW_TOKEN_SAFETY_MARGIN | Fraction of the input budget actually used, absorbing token-estimate error | 0.9 |
REVIEW_MAX_DIFF_LINES | Line cap on single-call diff renders (/describe, /changelog, /add-docs, replies, budgeting-disabled review). Token-budgeted reviews ignore it (planner owns coverage by tokens); 0 disables the cap | 5000 |
THRILLHOUSEBOT_REVIEW_MAX_REVIEW_COMMENTS | Maximum inline comments posted per review; findings over the cap are surfaced in the summary instead of dropped | 50 |
THRILLHOUSEBOT_REVIEW_MAX_AI_RETRIES | Attempts per failed AI call before the review errors out | 5 |
THRILLHOUSEBOT_REVIEW_AI_RETRY_BASE_DELAY_MS | Base delay of the exponential retry backoff, in milliseconds | 2000 |
THRILLHOUSEBOT_REVIEW_AI_TIMEOUT_SECONDS | Client-side wait per AI streaming attempt; keep it >= AI_TIMEOUT so timed-out attempts don't leave orphaned provider streams | 300 |
THRILLHOUSEBOT_REVIEW_INSTRUCTIONS_FILE | Repo-relative path of the per-repo instructions file read on each review | .github/thrillhousebot.md |
THRILLHOUSEBOT_REVIEW_IGNORED_FILES | Comma-separated gitignore-style globs excluded from review — lockfiles, generated code, build output. * does not cross /; use ** to span directories. Replaces (not extends) the default list, so re-include the defaults you still want | **/pom.xml,**/package-lock.json,**/*.lock,**/*.generated.*,**/target/** |
REVIEW_LABELS_ENABLED | Opt in to context-aware PR labels (see PR labels) | false |
REVIEW_LABELS_APPLY | When labels are enabled, add them to the PR instead of only suggesting them in a comment | false |
REVIEW_LABELS_ALLOW_CREATE | Allow the bot to create suggested labels that don't exist yet | false |
REVIEW_LABELS_MAX | Maximum labels applied or suggested per PR | 3 |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET | OAuth credentials for dashboard login | (required for dashboard) |
DASHBOARD_URL | Public dashboard URL (OAuth callback base) | http://localhost:8080 |
DATASOURCE_DB_KIND | h2 or postgresql | h2 (dev), postgresql (%prod) |
HTTP_CONNECT_TIMEOUT | Outbound HTTP connect timeout (GitHub API, OAuth) | 10s |
HTTP_REQUEST_TIMEOUT | Outbound HTTP request timeout (GitHub API, OAuth) | 10s |
WEBSOCKET_KEEPALIVE_MS | Dashboard WebSocket keepalive interval in ms; 0 or negative disables it (and stale replay-buffer eviction) | 25000 |
AI call budget
Section titled “AI call budget”A review that reports findings makes two model calls by default, not one:
the review call itself plus a verification call that re-sends the diff and the
candidate findings, so budget roughly 2× tokens per flagged review. On
large PRs under token-aware budgeting this becomes N batch review calls + N
per-batch verification calls + one summary call. Set
REVIEW_VERIFIER_ENABLED=false to skip only the AI verifier — cheaper, at the
cost of more false positives; a deterministic hedging guard still runs, and a
verifier failure never blocks the review (it fails open, keeping the original
findings).
The app validates configuration at startup and fails fast if a required value
(GITHUB_APP_ID, GITHUB_PRIVATE_KEY, GITHUB_WEBHOOK_SECRET, AI_API_KEY) is missing or — for
the private key — not a valid PEM RSA key, naming every offending variable in one message instead of
surfacing later on the first webhook or review. Dashboard OAuth (GITHUB_CLIENT_ID /
GITHUB_CLIENT_SECRET) is optional: leave both unset and the dashboard login is simply disabled.
Cost tracking uses per-model pricing keyed by the model name, for example:
thrillhousebot.ai.pricing.deepseek-chat.input-per-1k=0.00014thrillhousebot.ai.pricing.deepseek-chat.output-per-1k=0.00028If you switch to a different AI_MODEL, add a matching
thrillhousebot.ai.pricing.<model>.* pair so the dashboard can compute cost.
Without an entry the bot still records tokens, but warns once and flags sessions
as "no pricing" instead of showing $0.
Per-model AI settings
Section titled “Per-model AI settings”Model-specific settings live under thrillhousebot.ai.models.<model>.*, keyed
by the model name (the AI_MODEL value) like the pricing map. Only the active
model's entry is read, so you can keep entries for every model you use and
switch AI_MODEL freely:
# Input hard cap (the model's context window). The effective review budget is# min(REVIEW_MAX_INPUT_TOKENS, cap); models without an entry get a 128000 cap.thrillhousebot.ai.models.deepseek-chat.max-input-tokens=64000# Per-model overrides of REVIEW_OUTPUT_BUFFER_TOKENS / REVIEW_TOKEN_SAFETY_MARGINthrillhousebot.ai.models.deepseek-chat.output-buffer-tokens=8192thrillhousebot.ai.models.deepseek-chat.token-safety-margin=0.9# Generation parameters, sent on every chat call when setthrillhousebot.ai.models.deepseek-chat.temperature=0.2thrillhousebot.ai.models.deepseek-chat.top-p=0.95thrillhousebot.ai.models.deepseek-chat.max-output-tokens=8192Notes:
max-input-tokensis a cap, not the budget.REVIEW_MAX_INPUT_TOKENSstays the spend knob; the per-model value keeps it from overshooting the model's real window. To use a large-context model beyond 128k, raise both. Startup logs a warning whenever the cap lowers your configured budget.- Quote keys with
.or/(thrillhousebot.ai.models."gpt-5.5".…), the same rule as the pricing map. Override via env — hyphen-only keys use underscores (THRILLHOUSEBOT_AI_MODELS_DEEPSEEK_V4_PRO_MAX_INPUT_TOKENS=1000000); dotted keys use the quoted-key form (THRILLHOUSEBOT_AI_MODELS__GPT_5_5__MAX_INPUT_TOKENS=256000).application.propertiesships empty stubs for known models so SmallRye can disambiguate hyphenated keys — Quarkus env mapping. For a model without a stub, add an emptythrillhousebot.ai.models."<model>".max-input-tokens=line (externalapplication.propertiesor-D) alongside the env var. top_kis not available on the OpenAI-compatible wire; it becomes relevant only with native provider integrations.- Generation-parameter validation happens at boot: temperature must be in
[0, 2],top-pin(0, 1], token counts positive — a typo in any entry (even an inactive model's) fails startup with a message naming the key.
PR labels
Section titled “PR labels”ThrillhouseBot can suggest context-aware labels (area, change type, risk) drawn
from the diff. The feature is off by default; turn it on with
REVIEW_LABELS_ENABLED=true.
When enabled, the model is shown the repository's existing labels and picks the
few that best describe the PR — it only ever chooses from labels that already
exist, so it respects whatever label scheme the repo already uses. What happens
next depends on REVIEW_LABELS_APPLY:
false(default): the suggestions are posted as a one-line comment on the first review, leaving the decision to a maintainer.true: the labels are added to the PR automatically.
Set REVIEW_LABELS_ALLOW_CREATE=true to let the bot create a suggested label
that doesn't exist yet (off by default, so it never invents labels), and
REVIEW_LABELS_MAX to cap how many labels it applies or suggests (default 3).
Labelling is best-effort — a failure here never blocks or fails the review.