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=*MCP_READ_REPO_ALLOWLIST is the same fail-closed/wildcard model, kept as a separate allowlist so read-only MCP tools (repo context, issue quality, watch subscriptions) can be granted independently of actuation trust. The full */all wildcard additionally unlocks the non-repo-scoped contributor/operator tools.
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:982` |
| `AI_EMBED_API_KEY` | `src/server.ts:440` |
| `AI_EMBED_BASE_URL` | `src/server.ts:437` |
| `AI_EMBED_MODEL` | `src/selfhost/ai.ts:872` |
| `AI_ON_MERGE` | `src/selfhost/ai.ts:984` |
| `AI_PROVIDER` | `src/selfhost/ai-config.ts:43` |
| `ANTHROPIC_AI_BASE_URL` | `src/selfhost/ai.ts:876` |
| `ANTHROPIC_AI_MODEL` | `src/selfhost/ai.ts:85` |
| `ANTHROPIC_API_KEY` | `src/selfhost/ai.ts:875` |
| `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_CHECK_INTERVAL_MS` | `src/selfhost/foreground-liveness.ts:52` |
| `FOREGROUND_LIVENESS_ENABLED` | `src/selfhost/foreground-liveness.ts:41` |
| `FOREGROUND_LIVENESS_MAX_DEFER_MS` | `src/selfhost/foreground-liveness.ts:51` |
| `FOREGROUND_LIVENESS_MAX_RELEASE_PER_SWEEP` | `src/selfhost/foreground-liveness.ts:53` |
| `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` |
| `GITHUB_INSTALLATION_CONCURRENCY_DEFER_MS` | `src/selfhost/installation-concurrency-admission.ts:47` |
| `GITHUB_INSTALLATION_CONCURRENCY_ENABLED` | `src/selfhost/installation-concurrency-admission.ts:34` |
| `GITHUB_INSTALLATION_CONCURRENCY_LIMIT` | `src/selfhost/installation-concurrency-admission.ts:43` |
| `GITTENSORY_REPO_CONFIG_DIR` | `src/server.ts:288` |
| `GITTENSORY_VERSION` | `src/selfhost/otel.ts:62` |
| `HOME` | `src/selfhost/ai.ts:302` |
| `MAINTENANCE_ADMISSION_DEFER_MS` | `src/selfhost/maintenance-admission.ts:171` |
| `MAINTENANCE_ADMISSION_DRAIN_AGE_MS` | `src/selfhost/maintenance-admission.ts:145` |
| `MAINTENANCE_ADMISSION_ENABLED` | `src/selfhost/maintenance-admission.ts:126` |
| `MAINTENANCE_ADMISSION_MAX_BACKLOG_CONVERGENCE_PENDING` | `src/selfhost/maintenance-admission.ts:167` |
| `MAINTENANCE_ADMISSION_MAX_DEFER_AGE_MS` | `src/selfhost/maintenance-admission.ts:141` |
| `MAINTENANCE_ADMISSION_MAX_LIVE_AGE_MS` | `src/selfhost/maintenance-admission.ts:155` |
| `MAINTENANCE_ADMISSION_MAX_LIVE_PENDING` | `src/selfhost/maintenance-admission.ts:151` |
| `MAINTENANCE_ADMISSION_MAX_PENDING` | `src/selfhost/maintenance-admission.ts:159` |
| `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:869` |
| `OLLAMA_AI_BASE_URL` | `src/selfhost/ai.ts:865` |
| `OLLAMA_AI_MODEL` | `src/selfhost/ai.ts:89` |
| `OPENAI_AI_BASE_URL` | `src/selfhost/ai.ts:867` |
| `OPENAI_AI_MODEL` | `src/selfhost/ai.ts:90` |
| `OPENAI_API_KEY` | `src/selfhost/ai.ts:869` |
| `OPENAI_COMPATIBLE_AI_API_KEY` | `src/selfhost/ai.ts:869` |
| `OPENAI_COMPATIBLE_AI_BASE_URL` | `src/selfhost/ai.ts:868` |
| `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` |
| `PGPOOL_MAX` | `src/selfhost/queue-common.ts:713` |
| `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` |
| `QUEUE_CONCURRENCY` | `src/selfhost/pg-queue.ts:285` |
| `QUEUE_DEAD_LETTER_AUTO_RETRY_MAX_EXTRA_ATTEMPTS` | `src/selfhost/queue-common.ts:721` |
| `QUEUE_STARTUP_JITTER_MIN_JOBS` | `src/selfhost/queue-common.ts:702` |
| `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. If either a per-repo file or the dir-root global default (.gittensory.yml at the mount root) exists, the public repo .gittensory.yml is never fetched for that review. With only one of the two present, its contents are used as-is; with both present, they are deep-merged — the per-repo file overlaid onto the global default, nested mappings merging key by key and arrays replacing wholesale.
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.