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
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.
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.# 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.
GITHUB_CACHE_TTL_SECONDS=20
GITHUB_BRANCH_PROTECTION_CACHE_TTL_SECONDS=1200
GITHUB_METADATA_CACHE_TTL_SECONDS=600GITHUB_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.
| Name | First reference |
| --- | --- |
| `AI_COMBINE` | `src/selfhost/ai.ts:968` |
| `AI_EMBED_API_KEY` | `src/server.ts:440` |
| `AI_EMBED_BASE_URL` | `src/server.ts:437` |
| `AI_EMBED_MODEL` | `src/selfhost/ai.ts:864` |
| `AI_ON_MERGE` | `src/selfhost/ai.ts:970` |
| `AI_PROVIDER` | `src/selfhost/ai-config.ts:43` |
| `ANTHROPIC_AI_BASE_URL` | `src/selfhost/ai.ts:868` |
| `ANTHROPIC_AI_MODEL` | `src/selfhost/ai.ts:85` |
| `ANTHROPIC_API_KEY` | `src/selfhost/ai.ts:867` |
| `BACKUP_ACKNOWLEDGED` | `src/server.ts:379` |
| `BROWSER_WS_ENDPOINT` | `src/selfhost/stubs/puppeteer.ts:11` |
| `CLAUDE_AI_EFFORT` | `src/selfhost/ai.ts:136` |
| `CLAUDE_AI_MODEL` | `src/selfhost/ai.ts:77` |
| `CLAUDE_AI_TIMEOUT_MS` | `src/selfhost/ai.ts:136` |
| `CODEX_AI_EFFORT` | `src/selfhost/ai.ts:140` |
| `CODEX_AI_MODEL` | `src/selfhost/ai.ts:81` |
| `CODEX_AI_TIMEOUT_MS` | `src/selfhost/ai.ts:140` |
| `CODEX_HOME` | `src/selfhost/ai.ts:302` |
| `CRON_INTERVAL_MS` | `src/server.ts:916` |
| `DATABASE_PATH` | `src/server.ts:249` |
| `DATABASE_URL` | `src/selfhost/preflight.ts:201` |
| `DISCORD_REPO_WEBHOOKS` | `src/services/notify-discord.ts:41` |
| `DISCORD_WEBHOOK_URL` | `src/services/notify-discord.ts:78` |
| `FOREGROUND_LIVENESS_ENABLED` | `src/selfhost/foreground-liveness.ts:41` |
| `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:508` |
| `GITTENSORY_REPO_CONFIG_DIR` | `src/server.ts:288` |
| `GITTENSORY_VERSION` | `src/selfhost/otel.ts:62` |
| `HOME` | `src/selfhost/ai.ts:302` |
| `MAINTENANCE_ADMISSION_ENABLED` | `src/selfhost/maintenance-admission.ts:126` |
| `MIGRATIONS_DIR` | `src/server.ts:392` |
| `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:861` |
| `OLLAMA_AI_BASE_URL` | `src/selfhost/ai.ts:857` |
| `OLLAMA_AI_MODEL` | `src/selfhost/ai.ts:89` |
| `OPENAI_AI_BASE_URL` | `src/selfhost/ai.ts:859` |
| `OPENAI_AI_MODEL` | `src/selfhost/ai.ts:90` |
| `OPENAI_API_KEY` | `src/selfhost/ai.ts:861` |
| `OPENAI_COMPATIBLE_AI_API_KEY` | `src/selfhost/ai.ts:861` |
| `OPENAI_COMPATIBLE_AI_BASE_URL` | `src/selfhost/ai.ts:860` |
| `OPENAI_COMPATIBLE_AI_MODEL` | `src/selfhost/ai.ts:91` |
| `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:965` |
| `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:967` |
| `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:229` |
| `PORT` | `src/server.ts:715` |
| `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:527` |
| `QUEUE_BACKGROUND_CONCURRENCY` | `src/selfhost/queue-common.ts:130` |
| `REDIS_URL` | `src/selfhost/preflight.ts:144` |
| `REVIEW_AUDIT_DIR` | `src/server.ts:572` |
| `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:832` |
| `SLACK_WEBHOOK_URL` | `src/services/notify-discord.ts:173` |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.
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=falseGITTENSORY_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.
gittensory-config/
owner__repo/.gittensory.yml
repo-name/.gittensory.yml
owner__repo.yml
.gittensory.ymlgate:
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: falseInstance-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.