Skip to content
Self-hosting

Configuration

The self-host configuration model: deployment env, private per-repo policy, feature flags, and review modes.

This page is the exhaustive reference. For the short path — the required secrets plus a conservative first-boot config — start with .env.selfhost.example in Quickstart instead.

Config layers

Environment
Deployment-wide infrastructure, secrets, feature kill switches, and service URLs. Requires restart or recreate when changed.
Private repo config
Mounted GITTENSORY_REPO_CONFIG_DIR files for private per-repo policy. Read fresh each review.
Public repo config
The repo .gittensory.yml. Useful for transparent policy, but not for thresholds or rules you need to keep private.
Built-in defaults
Safe fallback when nothing is configured. Gate off, AI off, and no repo runs per-PR features until allowlisted.

Required baseline env

.env
PUBLIC_API_ORIGIN=https://reviews.example.com
GITHUB_APP_ID=123456
GITHUB_APP_SLUG=my-gittensory-app
GITHUB_APP_PRIVATE_KEY_FILE=/run/secrets/github-app-private-key.pem
GITHUB_WEBHOOK_SECRET=<random-webhook-secret>

GITTENSOR_REGISTRY_URL=https://example.invalid/registry.json
GITTENSORY_API_TOKEN=<random-32-byte-token>
GITTENSORY_MCP_TOKEN=<random-32-byte-token>
INTERNAL_JOB_TOKEN=<random-32-byte-token>

Any FOO_FILE is loaded into FOO at startup. Explicit FOO wins over the file variant.

MCP_ACTUATION_REPO_ALLOWLIST
GITTENSORY_MCP_TOKEN is a shared, end-user-obtainable CLI credential (the normal alternative to gittensory-mcp login), so it must not implicitly stage actions (merges, closes, approvals) on every repo the App happens to be installed on. MCP_ACTUATION_REPO_ALLOWLIST scopes it to an explicit, comma/whitespace-separated owner/repo list — unset denies all actuation for this token. Set it to * or all to opt back into the pre-scoping, any-repo behavior. If you already rely on GITTENSORY_MCP_TOKEN for approval-queue actuation, set this variable after upgrading or MCP actuation stops working.
.env
# Deny-by-default: unset means the static MCP token cannot stage or decide any action.
MCP_ACTUATION_REPO_ALLOWLIST=owner/repo-one, owner/repo-two
# Restore pre-upgrade any-repo behavior:
# MCP_ACTUATION_REPO_ALLOWLIST=*

GitHub API cache

Redis backs shared caching for stable GitHub GET responses, including repeated installation, repo/user metadata, and branch-protection required-status reads. Keys include the caller identity and response-shaping headers, and cold misses are single-flighted so concurrent jobs do not stampede GitHub.

.env
GITHUB_CACHE_TTL_SECONDS=20
GITHUB_BRANCH_PROTECTION_CACHE_TTL_SECONDS=1200
GITHUB_METADATA_CACHE_TTL_SECONDS=600
GITHUB_CACHE_TTL_SECONDS is the short default for repeated safe GitHub GETs. Stable repo/user metadata and branch-protection required-status reads use the per-class TTLs above so operators can keep repeated policy reads hot without broadening stale cache risk. Live CI status, check-run, check-suite, pull/issue subresources, pull mergeability, token minting, rate-limit, and collaborator-permission endpoints are never served from this cache. Prometheus exports gittensory_github_response_cache_total, and the bundled self-host Grafana dashboard includes the hit/miss/coalesced/error breakdown.

Generated env reference

This table is generated from process.env.NAME reads in src/selfhost/** and src/server.ts. It intentionally includes names and first source references only, never example values.

self-host env vars
| Name | First reference |
| --- | --- |
| `AI_COMBINE` | `src/selfhost/ai.ts:930` |
| `AI_EMBED_API_KEY` | `src/server.ts:422` |
| `AI_EMBED_BASE_URL` | `src/server.ts:419` |
| `AI_EMBED_MODEL` | `src/selfhost/ai.ts:826` |
| `AI_ON_MERGE` | `src/selfhost/ai.ts:932` |
| `AI_PROVIDER` | `src/selfhost/ai-config.ts:43` |
| `ANTHROPIC_AI_BASE_URL` | `src/selfhost/ai.ts:830` |
| `ANTHROPIC_AI_MODEL` | `src/selfhost/ai.ts:57` |
| `ANTHROPIC_API_KEY` | `src/selfhost/ai.ts:829` |
| `BACKUP_ACKNOWLEDGED` | `src/server.ts:361` |
| `BROWSER_WS_ENDPOINT` | `src/selfhost/stubs/puppeteer.ts:11` |
| `CLAUDE_AI_EFFORT` | `src/selfhost/ai.ts:108` |
| `CLAUDE_AI_MODEL` | `src/selfhost/ai.ts:49` |
| `CLAUDE_AI_TIMEOUT_MS` | `src/selfhost/ai.ts:108` |
| `CODEX_AI_EFFORT` | `src/selfhost/ai.ts:112` |
| `CODEX_AI_MODEL` | `src/selfhost/ai.ts:53` |
| `CODEX_AI_TIMEOUT_MS` | `src/selfhost/ai.ts:112` |
| `CODEX_HOME` | `src/selfhost/ai.ts:274` |
| `CRON_INTERVAL_MS` | `src/server.ts:864` |
| `DATABASE_PATH` | `src/server.ts:244` |
| `DATABASE_URL` | `src/selfhost/preflight.ts:201` |
| `DISCORD_REPO_WEBHOOKS` | `src/selfhost/discord-notify.ts:31` |
| `DISCORD_WEBHOOK_URL` | `src/selfhost/discord-notify.ts:40` |
| `GITHUB_APP_ID` | `src/selfhost/orb-collector.ts:59` |
| `GITHUB_APP_PRIVATE_KEY` | `src/selfhost/orb-collector.ts:166` |
| `GITHUB_CACHE_TTL_SECONDS` | `src/server.ts:490` |
| `GITTENSORY_REPO_CONFIG_DIR` | `src/server.ts:278` |
| `GITTENSORY_VERSION` | `src/selfhost/health.ts:29` |
| `HOME` | `src/selfhost/ai.ts:274` |
| `MAINTENANCE_ADMISSION_ENABLED` | `src/selfhost/maintenance-admission.ts:83` |
| `MIGRATIONS_DIR` | `src/server.ts:374` |
| `OBSERVABILITY_SMOKE_POLL_MS` | `scripts/smoke-observability-traces.mjs:8` |
| `OBSERVABILITY_SMOKE_TIMEOUT_MS` | `scripts/smoke-observability-traces.mjs:6` |
| `OLLAMA_AI_API_KEY` | `src/selfhost/ai.ts:823` |
| `OLLAMA_AI_BASE_URL` | `src/selfhost/ai.ts:819` |
| `OLLAMA_AI_MODEL` | `src/selfhost/ai.ts:61` |
| `OPENAI_AI_BASE_URL` | `src/selfhost/ai.ts:821` |
| `OPENAI_AI_MODEL` | `src/selfhost/ai.ts:62` |
| `OPENAI_API_KEY` | `src/selfhost/ai.ts:823` |
| `OPENAI_COMPATIBLE_AI_API_KEY` | `src/selfhost/ai.ts:823` |
| `OPENAI_COMPATIBLE_AI_BASE_URL` | `src/selfhost/ai.ts:822` |
| `OPENAI_COMPATIBLE_AI_MODEL` | `src/selfhost/ai.ts:63` |
| `ORB_AIR_GAP` | `src/selfhost/orb-collector.ts:161` |
| `ORB_ANONYMIZE` | `src/selfhost/orb-collector.ts:174` |
| `ORB_APP_ID` | `src/selfhost/orb-collector.ts:59` |
| `ORB_BROKER_URL` | `src/server.ts:913` |
| `ORB_COLLECTOR_TOKEN` | `src/selfhost/orb-collector.ts:205` |
| `ORB_COLLECTOR_URL` | `src/selfhost/orb-collector.ts:172` |
| `ORB_ENROLLMENT_SECRET` | `src/selfhost/orb-collector.ts:165` |
| `ORB_RELAY_MODE` | `src/server.ts:915` |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `src/selfhost/otel.ts:47` |
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | `src/selfhost/otel.ts:45` |
| `OTEL_SERVICE_ENVIRONMENT` | `src/selfhost/otel.ts:60` |
| `OTEL_SERVICE_NAME` | `src/selfhost/otel.ts:59` |
| `OTEL_TRACES_EXPORTER` | `src/selfhost/otel.ts:40` |
| `OTEL_TRACES_SAMPLER` | `src/selfhost/otel.ts:74` |
| `OTEL_TRACES_SAMPLER_ARG` | `src/selfhost/otel.ts:76` |
| `PGVECTOR_ENABLED` | `src/server.ts:224` |
| `PORT` | `src/server.ts:663` |
| `PUBLIC_API_ORIGIN` | `src/selfhost/preflight.ts:192` |
| `QDRANT_API_KEY` | `src/selfhost/qdrant-vectorize.ts:50` |
| `QDRANT_DIM` | `src/selfhost/qdrant-vectorize.ts:71` |
| `QDRANT_URL` | `src/server.ts:509` |
| `QUEUE_BACKGROUND_CONCURRENCY` | `src/selfhost/queue-common.ts:102` |
| `REDIS_URL` | `src/selfhost/preflight.ts:144` |
| `REVIEW_AUDIT_DIR` | `src/server.ts:554` |
| `SELFHOST_BUNDLE_ALL` | `scripts/build-selfhost.mjs:13` |
| `SELFHOST_SERVICE` | `scripts/smoke-observability-traces.mjs:5` |
| `SELFHOST_SETUP_TOKEN` | `src/selfhost/preflight.ts:186` |
| `SENTRY_DSN` | `src/selfhost/sentry.ts:365` |
| `SENTRY_ENVIRONMENT` | `src/selfhost/otel.ts:60` |
| `SENTRY_RELEASE` | `src/selfhost/otel.ts:62` |
| `SENTRY_SERVER_NAME` | `src/selfhost/sentry.ts:383` |
| `SENTRY_TRACES_SAMPLE_RATE` | `src/selfhost/sentry.ts:171` |
| `SETUP_OUTPUT_PATH` | `src/server.ts:780` |

Per-PR feature flags

Most review capabilities need both their own flag and the repo in GITTENSORY_REVIEW_REPOS. This gives you a global kill switch and a per-repo rollout switch.

.env
GITTENSORY_REVIEW_REPOS=owner/repo,owner/another
GITTENSORY_REVIEW_UNIFIED_COMMENT=true
GITTENSORY_REVIEW_INLINE_COMMENTS=false
GITTENSORY_REVIEW_SAFETY=true
GITTENSORY_REVIEW_GROUNDING=true
GITTENSORY_REVIEW_RAG=false
GITTENSORY_REVIEW_ENRICHMENT=false
GITTENSORY_REVIEW_REPUTATION=false
Empty GITTENSORY_REVIEW_REPOS means no repos run the per-PR feature path, regardless of the individual flags.

Private per-repo config

Mount a gitignored directory and point GITTENSORY_REPO_CONFIG_DIR at it. The first matching file wins and replaces the public repo config for that review.

config directory
gittensory-config/
  owner__repo/.gittensory.yml
  repo-name/.gittensory.yml
  owner__repo.yml
  .gittensory.yml
owner__repo/.gittensory.yml
gate:
  enabled: true
  aiReview:
    mode: advisory
    allAuthors: true
settings:
  commentMode: all_prs
  includeMaintainerAuthors: true
  autonomy:
    merge: observe
    close: observe
  agentDryRun: false
features:
  safety: true
  unifiedComment: true
  rag: false
  reputation: false

Instance-wide write switches

Unset
Normal mode. Per-repo autonomy and GitHub permissions decide what can be written.
dry-run
Compute reviews and audit as shadow, but suppress comments, checks, labels, merges, and closes.
disabled
Suppress writes as denied. Use when you need a hard instance-wide stop.

Next steps

Configure the GitHub integration in GitHub App and Orb, then add optional context through AI providers, REES, or RAG.