Type something to search...
Browse documentation

Environment variable reference

ProPR and ProCursor read their startup settings from the process environment. There is no configuration file to edit; everything else is configured in the management UI.

Defaults and accepted ranges are stated here. Other pages name a variable and link to this one.

How a value can fail to take effect

A wrong value behaves in one of three ways:

What you didWhat happens
Set it somewhere the container never seesSilently ignored. See the example stack below.
Set it to something unparsable - a word where a number belongs, or a blank valueSilently ignored; the default is used.
Set it to a parsable value outside the accepted rangeStartup fails for most settings, with a message naming the setting and its bounds.

The following settings depart from that:

  • MEISTER_SESSION_IDLE_MINUTES and MEISTER_SESSION_ABSOLUTE_HOURS fail startup on anything that is not a positive integer. They do not fall back to their default.
  • The MEISTER_PLATFORM_ADMIN_SSO_* variables fail startup on any value outside the accepted ones, and when one of them is set without MEISTER_PLATFORM_ADMIN_SSO_AUTHORITY. See single sign-on for platform administrators.
  • The MEISTER_PROVISIONED_USER_SSO_* variables fail startup on any value outside the accepted ones, and when one of them is set without MEISTER_PROVISIONED_USER_SSO_AUTHORITY. See single sign-on for provisioned users.
  • MEISTER_LOCAL_SIGN_IN_ENABLED and MEISTER_TENANT_SSO_ENABLED accept true or false without regard to case or surrounding whitespace. Unset, blank, or whitespace-only values use the default true; other unparsable values fail startup. See installation sign-in policy.
  • MEISTER_PLATFORM_ADMIN_ELEVATION_MAX_MINUTES fails startup on anything that is not a whole number from 1 to 1440. It does not fall back to its default.
  • MEISTER_PROVISIONED_USER_PAT_MAX_DAYS fails startup unless it is unset or a whole number from 1 to 3650.
  • MEISTER_DEFAULT_ENABLED_PROVIDERS fails startup when it names a provider family other than azureDevOps, github, gitLab or forgejo, or when a set value names no family. See enabling a provider family.
  • A value under the minimum of an interval marked “clamped” is silently raised to that minimum.
  • Most AI_* review-tuning settings are not range-checked. An out-of-range value is used as given. Their listed ranges describe supported operation. The two evidence judge budgets are validated at startup.

Other symptoms, and the page that fixes each: troubleshooting.

Does the example stack forward it?

Compose passes a variable to a container only when the service’s environment: block names it. The bundled example/docker-compose/docker-compose.yml names a fixed set. Anything else you put in .env is silently ignored.

In an env file, do not put spaces around the equals sign. Write KEY=value, not KEY = value - the spaces become part of the name and the value.

The last column of every table below says which:

ValueMeaning
yesThe compose file forwards it from .env
pinnedThe compose file sets a fixed value; .env cannot change it
noNot forwarded. Setting it in .env does nothing.

To use one that is not forwarded, add it to the meisterpropr service’s environment: block yourself:

    environment:
      - MEISTER_ALLOW_PRIVATE_EGRESS=${MEISTER_ALLOW_PRIVATE_EGRESS:-}

Required values

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_JWT_SECRETSigning secret for session access tokensnoneat least 32 charactersyes
MEISTER_BOOTSTRAP_ADMIN_USERUsername of the admin account seeded on first startnone-yes
MEISTER_BOOTSTRAP_ADMIN_PASSWORDPassword for that accountnone-yes
DB_CONNECTION_STRINGPostgreSQL connection string for the ProPR databasenone-yes
PROCURSOR_SHARED_KEYShared secret the API and ProCursor authenticate to each other withnone-yes
PROCURSOR_DB_CONNECTION_STRINGPostgreSQL connection string for the ProCursor databasenone-yes
PROCURSOR_PROPR_BASE_URLInternal base URL ProCursor calls the API onnoneabsolute URLyes

What happens when one of them is absent:

  • The bootstrap admin values are read only when no active admin user exists. When one does, they are ignored. When none does and they are absent, startup fails.
  • ProPR registers its database-backed features only when DB_CONNECTION_STRING is set.
  • The ProCursor service refuses to start without its own connection string, its shared key, and the API base URL. On the API side, an absent shared key or service base URL turns ProCursor off - see running without ProCursor.
  • MEISTER_JWT_SECRET is read when the first token is signed, not at startup. A missing or too-short secret lets the stack come up healthy and fails the first sign-in.
  • On the bundled compose stack, an absent PROCURSOR_SHARED_KEY does not turn ProCursor off. That stack always defines the service and gates the API on it, so ProCursor crash-loops and the API never starts. See running without ProCursor.

Use a long random string for MEISTER_JWT_SECRET and PROCURSOR_SHARED_KEY. What rotating the JWT secret costs: sign-in and sessions.

Public URL and browser origins

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_PUBLIC_BASE_URLThe externally reachable API base URL, used for webhook listener URLs, SSO redirects and the allowed browser originfalls back to the request hostabsolute URL, including the proxy’s /api prefix if it has oneyes
CORS_ORIGINSExtra browser origins allowed to call the APInonecomma-separated originsyes

See deployment topology for why both matter behind your own ingress.

Encryption key ring

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_DATA_PROTECTION_KEYS_PATHDirectory holding the key ring that encrypts stored provider and AI credentials and the activated licenseunset - keys live on the container’s own filesystem and are lost when it is replacedProPR creates the writable directory if absent. Relative paths resolve against the process working directory, and whitespace is preserved.pinned
MEISTER_DATA_PROTECTION_PROTECTOREncrypts the key files themselves, so a copy of the key directory reads nothing on its ownnonenone, certificate, or the name of a protector add-inno
MEISTER_DATA_PROTECTION_ADD_IN_DIRECTORYDirectory protector add-ins are read fromdata-protection-add-ins beside the service’s own files, which is where the images put the add-ins they shipdirectory pathno

The example stack pins the key path to a fixed in-container path and mounts a named volume there, shared by both services. To put the key ring on durable storage of your own, edit that line and mount your volume in its place.

Both services must be given the same path and the same protector. Give them an absolute path, or the same relative path under working directories backed by the same volume. Relative paths resolve against each process’s working directory. Different working directories can therefore select different key rings.

What the key ring protects and what to back up with it: the encryption key ring.

The certificate protector

MEISTER_DATA_PROTECTION_PROTECTOR=certificate encrypts the key files with a PKCS#12 certificate you hold.

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_DATA_PROTECTION_CERTIFICATE_PATHThe certificate the key files are encrypted withnonepath to a .pfx fileno
MEISTER_DATA_PROTECTION_CERTIFICATE_PASSWORDThe password of the certificate filesnonethe password, or unset for a file without oneno
MEISTER_DATA_PROTECTION_PREVIOUS_CERTIFICATE_PATHSCertificates an earlier key was encrypted with, which stay readable during a rotationnonepaths separated by ;, on every platformno

One password applies to every file in the list, the current certificate and the previous ones alike. To rotate to a certificate carrying a password of its own, export the superseded certificate again under the new password, list that copy in MEISTER_DATA_PROTECTION_PREVIOUS_CERTIFICATE_PATHS, and set MEISTER_DATA_PROTECTION_CERTIFICATE_PASSWORD to the new password. Keep a superseded certificate listed until every key it encrypted has expired; a key removed from the list is a key nothing can read. The semicolon separates the entries, so a certificate whose path contains one cannot be listed: move or rename that file. A certificate the service cannot open, and a protector name nothing provides, both stop the service from starting, with the variable named.

The Azure Key Vault protector

MEISTER_DATA_PROTECTION_PROTECTOR=azure-key-vault wraps the key ring with a Key Vault key. Both service images carry this protector, so selecting it needs no rebuild.

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_DATA_PROTECTION_AZURE_KEY_VAULT_KEY_IDThe vault key the ring is wrapped withnonea key identifier URL, https://<vault>.vault.azure.net/keys/<key>/<version>no
MEISTER_DATA_PROTECTION_AZURE_BLOB_URIStores the key ring in a blob instead of a directoryunset: the ring stays where MEISTER_DATA_PROTECTION_KEYS_PATH pointsa blob URLno

The service authenticates to Azure with the credential described under a process-wide Azure credential. That identity needs wrap and unwrap on the key, and read and write on the blob where one is configured. Each URL you set must use https and name a host; the blob URI stays optional. A missing key identifier stops the service from starting, with the variable named. Give both services the same key, and the blob URI where you use one.

Sessions and sign-in protection

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_SESSION_IDLE_MINUTESIdle timeout, renewed by activity480any positive integerno
MEISTER_SESSION_ABSOLUTE_HOURSAbsolute session lifetime, fixed at sign-in72any positive integerno
MEISTER_AUTH_LOCKOUT_MAX_ATTEMPTSConsecutive failed passwords that trigger a lockout51–100no
MEISTER_AUTH_LOCKOUT_BASE_MINUTESLockout duration at the first threshold151–1440no
MEISTER_AUTH_LOCKOUT_MAX_MINUTESCap on the exponentially backed-off lockout duration601–10080no
MEISTER_AUTH_RATELIMIT_ENABLEDWhether the per-IP limiter on the auth endpoints runstruetrue, falseno
MEISTER_AUTH_RATELIMIT_PERMITSAuth requests permitted per window per client IP201–10000no
MEISTER_AUTH_RATELIMIT_WINDOW_SECONDSLength of that window601–3600no

What these controls are for: sign-in and sessions.

Single sign-on for platform administrators

Platform administrators can sign in through one OpenID Connect identity provider beside the local form at /login. This sign-in needs a commercial license that includes single sign-on (sso-authentication, see editions); without it, ProPR ignores the provider and offers the local form only. Setting MEISTER_PLATFORM_ADMIN_SSO_AUTHORITY turns the provider on. How the role decides the rights of an account is described under platform administrators and the identity provider of the installation.

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_PLATFORM_ADMIN_SSO_AUTHORITYThe issuer URL of the provider, for example https://id.example.com/realms/staffunset: platform administrators sign in with local accounts onlyabsolute https URL without credentials, query or fragment; http in Developmentno
MEISTER_PLATFORM_ADMIN_SSO_METADATA_URLThe address ProPR reads the provider’s discovery document from<authority>/.well-known/openid-configurationabsolute https URL without credentials or fragment; http in Developmentno
MEISTER_PLATFORM_ADMIN_SSO_CLIENT_IDThe client ID of ProPR at the providernonerequired with the authorityno
MEISTER_PLATFORM_ADMIN_SSO_CLIENT_SECRETThe client secret of ProPR at the providernonerequired with the authorityno
MEISTER_PLATFORM_ADMIN_SSO_ROLE_CLAIMThe ID token claim that lists the roles of the accountrolesthe name of a top-level claim whose value is a string or an array of stringsno
MEISTER_PLATFORM_ADMIN_SSO_REQUIRED_ROLEThe role that gives an account platform administrator rightsnonerequired with the authority; compared case-sensitivelyno
MEISTER_PLATFORM_ADMIN_SSO_DISPLAY_NAMEThe name on the sign-in buttonthe host of the authorityat most 80 characters on one lineno

Register ProPR at the provider as a confidential client that uses the authorization code flow, with <MEISTER_PUBLIC_BASE_URL>/auth/platform-admin/callback as its redirect URI. Without MEISTER_PUBLIC_BASE_URL, the redirect URI starts with the address the browser reaches the API on. When the user interface runs on another origin than MEISTER_PUBLIC_BASE_URL, list that origin in CORS_ORIGINS, because the sign-in returns the access token only to the origins named under the return address of a sign-in. ProPR sends a PKCE code challenge (S256) and a nonce, and it sends the client secret in the body of the token request (client_secret_post). It requests the scopes openid, profile and email.

The ID token must carry the role claim at its top level. Keycloak puts realm roles into the nested claim realm_access.roles by default, which ProPR does not read; add a realm role mapper to the client that writes the roles into a claim named roles of the ID token.

Set MEISTER_PLATFORM_ADMIN_SSO_METADATA_URL when ProPR reaches the provider at another address than the browser does, for example over an internal network. The discovery document at that address must name the authority as its issuer, or ProPR refuses every sign-in through the provider. Outside Development, the authorization endpoint and the token endpoint that the document names must use https as well.

Single sign-on for provisioned users

Commercial feature. This sign-in needs a commercial license that includes single sign-on (sso-authentication, see editions).

A hosting platform that runs ProPR for its customers provisions their accounts through the administration API, and these customers sign in through one OpenID Connect identity provider beside the other sign-ins at /login. Setting MEISTER_PROVISIONED_USER_SSO_AUTHORITY turns on the sign-in and the provisioning endpoints. How the platform decides who signs in is described under provisioned users and the identity provider of a hosting platform.

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_PROVISIONED_USER_SSO_AUTHORITYThe issuer URL of the provider, for example https://id.example.com/realms/customersunset: no sign-in of provisioned users, and the provisioning endpoints answer 404absolute https URL without credentials, query or fragment; http in Developmentno
MEISTER_PROVISIONED_USER_SSO_METADATA_URLThe address ProPR reads the provider’s discovery document from<authority>/.well-known/openid-configurationabsolute https URL without credentials or fragment; http in Developmentno
MEISTER_PROVISIONED_USER_SSO_CLIENT_IDThe client ID of ProPR at the providernonerequired with the authorityno
MEISTER_PROVISIONED_USER_SSO_CLIENT_SECRETThe client secret of ProPR at the providernonerequired with the authorityno
MEISTER_PROVISIONED_USER_SSO_DISPLAY_NAMEThe name on the sign-in buttonthe host of the authorityat most 80 characters on one lineno

Register ProPR at the provider as a confidential client that uses the authorization code flow, with <MEISTER_PUBLIC_BASE_URL>/auth/provisioned-user/callback as its redirect URI. ProPR reads the subject of the ID token and no role. The return address, PKCE, the nonce, the client secret in the body of the token request, the scopes and the metadata URL are the same as under single sign-on for platform administrators.

ProPR records every provisioned user under the authority without a trailing slash. After a change of MEISTER_PROVISIONED_USER_SSO_AUTHORITY, the users that the platform provisioned under the former authority no longer sign in, and the platform provisions them again.

Personal access tokens of provisioned users

MEISTER_PROVISIONED_USER_PAT_MAX_DAYS limits the lifetime of personal access tokens owned by provisioned users. Leave it unset to impose no installation limit, or set a whole number from 1 to 3650. Hosted cells can set it to 90. Platform administrators and accounts that were not provisioned are exempt.

ProPR assigns new tokens the earlier of the requested expiry and their creation time plus the limit. An omitted expiry uses the limit. Existing tokens also expire at their creation time plus the configured limit, even when they were issued without an expiry. An earlier stored expiry still applies. Token listing and authentication use the same effective expiry. The Personal Access Tokens page shows the applicable limit and the resulting expiry.

The setting takes effect after a restart. Lowering it can invalidate existing customer tokens immediately. Increasing or removing it can make an existing token usable again if its stored expiry and revocation state allow it. Revoke a token to end its access independently of future setting changes.

Installation sign-in policy

An installation that signs people in only through its own identity providers turns off the password sign-in and the identity providers of tenants. A cell of a hosting platform, whose people sign in through the providers for platform administrators and for provisioned users, sets both variables to false.

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_LOCAL_SIGN_IN_ENABLEDWhether people sign in with a user name and a password, at /login and at the local sign-in of a tenanttruetrue, falseno
MEISTER_TENANT_SSO_ENABLEDWhether the members of a tenant sign in through the identity providers that the tenant configurestruetrue, falseno

With MEISTER_LOCAL_SIGN_IN_ENABLED=false, ProPR refuses the password sign-in of every account, also in a tenant that allows local login. The sign-in page shows no password form. ProPR changes no password and creates no account with a password. Personal access tokens keep working, so scripts and a hosting platform call the API with the X-User-Pat header as before. ProPR still creates the bootstrap administrator from MEISTER_BOOTSTRAP_ADMIN_USER and MEISTER_BOOTSTRAP_ADMIN_PASSWORD when no active platform administrator exists. The password of that account works again once the variable is true.

Without the password sign-in, platform administrators sign in only through the identity provider for platform administrators. ProPR therefore refuses to start when MEISTER_PLATFORM_ADMIN_SSO_AUTHORITY is not set, when the license does not grant sso-authentication, and when the license cannot be read at startup. The error message contains the names of the variables. If the license stops granting sso-authentication while ProPR runs, platform administrators cannot sign in, and the next start fails until a license grants it again or the variable is true.

With MEISTER_TENANT_SSO_ENABLED=false, ProPR offers no identity provider of a tenant and refuses the sign-ins through these providers. A tenant administrator cannot create a provider or save one as enabled, and can still disable and delete providers. ProPR keeps the providers and offers the enabled ones again once the variable is true.

A session that started before either change runs until it ends.

Switching to installation identity providers

An installation keeps a way for administrators to sign in when it turns the sign-ins off in this order:

  1. Start a new installation with MEISTER_LOCAL_SIGN_IN_ENABLED=true so ProPR applies its migrations and creates the bootstrap administrator. License activation requires the administration API; --verify-license verifies a document without activating it.
  2. Configure the identity provider for platform administrators, and for a hosting platform the identity provider for provisioned users. Activate a license that grants sso-authentication. After the restart, the log of the start contains the message that platform administrators can sign in through the provider.
  3. Sign in through the provider with an account that holds the required role. ProPR gives the account platform administrator rights.
  4. If scripts or a hosting platform need API access, create their tokens under Settings → Personal Access Tokens. Tokens that exist keep working. An SSO administrator’s tokens are revoked when that account signs in without the required administrator role; use a dedicated account for automation.
  5. Set MEISTER_TENANT_SSO_ENABLED=false, so the members of tenants sign in through the identity providers of the installation only.
  6. Set MEISTER_LOCAL_SIGN_IN_ENABLED=false and restart. If the start fails, the error message contains the name of the variable to correct. With MEISTER_LOCAL_SIGN_IN_ENABLED=true, the installation starts as before.
  7. Open /login. The customer provider is the primary sign-in, and Staff access opens the administrator provider. An installation without a customer provider shows the administrator provider directly. The password form is hidden.

To use the password sign-in again, for example while the identity provider is unreachable, set MEISTER_LOCAL_SIGN_IN_ENABLED=true and restart. The bootstrap administrator then signs in with its password.

Review workers

VariableWhat it doesDefaultAcceptedExample stack
WORKER_MAX_CONCURRENT_REVIEW_JOBSHow many review jobs one API instance runs at once41–64no
WORKER_POLL_INTERVAL_MILLISECONDSHow often the worker looks for pending jobs200010–60000no
WORKER_STUCK_JOB_TIMEOUT_MINUTESRetired. Accepted and ignored; the worker warns at startup when it is set--no

WORKER_MAX_CONCURRENT_REVIEW_JOBS takes effect only with a commercial license that allows parallel review execution. Without one, ProPR runs one review at a time. Where the license allows fewer concurrent reviews than this setting, the license limit applies - see editions. How this setting and AI_MAX_FILE_REVIEW_CONCURRENCY combine into peak load: review workers.

A platform administrator can also limit the concurrent reviews of one tenant. That limit counts the reviews of the tenant on every instance and runner, and it applies together with this setting and the license limit - see tenant review limits.

The runner host

Read by the runner container, not by the control plane. The runner exposes a health endpoint and nothing else.

VariableWhat it doesDefaultAcceptedExample stack
RUNNER_CONTROL_PLANE_URLBase URL of the control plane it leases fromnoneabsolute URLno
RUNNER_CREDENTIALThe credential issued at enrollmentnone-no
RUNNER_REGISTRATION_TOKENOperator-issued token, spending one of its uses to enrollnone-no
RUNNER_DISPLAY_NAMEOperator-facing name in the registrythe machine name-no
RUNNER_TAGSTags this runner declaresnonecomma-separatedno
RUNNER_CAPACITYHow many jobs it runs at once21-64no
RUNNER_POLL_INTERVAL_SECONDSWait between asks when the last found no work51-3600no
RUNNER_MAX_BACKOFF_SECONDSBackoff ceiling while the control plane is unreachable605-3600no
RUNNER_WORK_ROOTWhere leased jobs are workeda temp directorywritable pathno
RUNNER_LOG_LEVELMinimum log levelInformationa Serilog level nameno

The control plane reads these two, not the runner. They govern the registry the fleet is listed from:

VariableMeaningDefaultRangeSecret
RUNNER_PRUNE_UNSEEN_DAYSHow long a runner may be silent before its row is removed. 0 keeps every row until an operator deletes it300-3650no
RUNNER_PRUNE_INTERVAL_SECONDSHow often the prune sweep runs360060+no

A runner holds its registration token and its credential in memory only, so a host that restarts enrolls again as a new runner and leaves its earlier row in the registry. The sweep deletes rows that have not been heard from within RUNNER_PRUNE_UNSEEN_DAYS. It skips a runner still holding a lease and removes it on a later sweep.

An enrolled runner counts towards the licensed number of runners as long as its credential is valid, whether or not the host is still running. A runner left behind by a restart therefore holds its place until the credential expires or the sweep deletes it. Deleting it on the Runners page frees the place immediately.

The runner reads OTLP_ENDPOINT like every other service. With none set it installs no exporter.

RUNNER_CONTROL_PLANE_URL must be https, except for loopback addresses. The credential is sent on every call.

Set RUNNER_REGISTRATION_TOKEN, not RUNNER_CREDENTIAL. Issue the token in the admin UI and give it to the host. The runner exchanges it for a credential on its first cycle and renews that credential before it expires. A host given neither reports that on /healthz and keeps running.

A registration token is single-use by default, and every enrollment spends one use. A restart is an enrollment, so a host you start by hand needs a fresh token each time it restarts, unless you issue the token for more than one use.

Issue a token for as many hosts as the deployment will start. A scaling group’s replicas come up without an operator present to issue each of them a token. Give the group one token whose enrollment count covers the replicas it may run, and keep it in the platform’s secret store. The Runners page shows each token’s remaining uses, and a token can be revoked at any point. To rotate one, issue the replacement before revoking the old token.

Both bounds are optional. Leave the lifetime empty for a token that does not expire, and the enrollment count empty for one with no limit. Set both to cover the scaling window you expect, and leave them empty only where you have decided a standing credential is acceptable.

A token is a credential. Whoever holds it can enroll as many hosts as its remaining uses allow, for as long as its lifetime runs. Keep an unbounded token in a secret store with a named owner, and rotate it on the cadence you would rotate any other standing credential.

To remove an enrolled host, revoke the runner, not its token. A token’s use is spent at enrollment, so revoking the token afterwards stops only the uses that remain. Revoking the runner makes every call it makes fail authentication. A lease it holds expires by itself, up to one lease duration later.

What touches the runner’s disk. The repository content under review sits in plaintext under RUNNER_WORK_ROOT while a job runs, and is purged when the job ends and again at startup. Everything else a review produces - trace, results, spend - is held in memory, batched to the control plane, and never written to disk. Purging cannot cover a host imaged or destroyed mid-job, so where disk remanence matters, put RUNNER_WORK_ROOT on an encrypted or ephemeral volume.

RUNNER_CREDENTIAL is for a host that already has one, such as a redeploy that must keep its registry identity. Leave it unset otherwise.

The runner requests a lease only when it has a free slot. An unreachable control plane, a full slot pool, an unsupported contract version and a drain are each reported on /healthz and retried with backoff. None of them exits the process. On shutdown the runner releases the leases it holds.

The runner also reads the AI_* review options, because it runs the review pipeline. Set them to the same values as the control plane. A runner with different values reviews differently, and nothing reports the difference. It reads no other AI_* value: connections, keys and model bindings stay on the control plane.

Runner fleet and queue stalls

A runner counts as active when it is enrolled, not revoked, speaks a contract version this control plane can serve, and was heard from inside RUNNER_ACTIVE_HEARTBEAT_WINDOW_SECONDS.

The control plane does not execute a job itself when an active runner is eligible for that job’s client. Jobs for clients no active runner can serve continue to run in the control plane. There is no setting that re-enables in-process execution for a job a runner could take.

VariableWhat it doesDefaultAcceptedExample stack
RUNNER_ACTIVE_HEARTBEAT_WINDOW_SECONDSHow recently a runner must have been heard from to count as capacity12015-3600no
RUNNER_FLEET_EMPTY_SETTLE_SECONDSHow long the fleet must be continuously empty before in-process execution resumes3000-3600no
RUNNER_QUEUE_STALL_GRACE_SECONDSHow long work may sit pending with no runner taking it before the queue is called stalled60030-86400no
RUNNER_ADVERTISED_URLBase URL this control-plane replica advertises to runners for job-scoped callsunsetabsolute https URL (loopback may be http)no

RUNNER_ACTIVE_HEARTBEAT_WINDOW_SECONDS must not exceed REVIEW_LEASE_DURATION_SECONDS, and startup fails when it does. A runner counted as available for longer than its leases survive leaves work with nobody to run it.

RUNNER_FLEET_EMPTY_SETTLE_SECONDS delays only the return to in-process execution. A runner becoming active takes effect at once.

A stalled queue reports one of NoActiveRunner, NoFreeSlot or NoRunnerMatchesRequiredTags.

Running more than one control-plane replica with runners requires RUNNER_ADVERTISED_URL on every replica. The replica that grants a lease serves that job: its workspace mirror is on local disk, and the job’s budget scope, tools and workspace registration live in its process. A runner configured with only a load-balanced URL reaches whichever replica the balancer picks, and that replica refuses the job’s calls as though the lease were lost. Set each replica’s own reachable address here. The lease carries it to the runner, which uses it for everything job-scoped and keeps the load-balanced URL for enrollment and for asking for work. Unset, the lease carries no address and runners use RUNNER_CONTROL_PLANE_URL for everything, which works for a single replica.

Review job leases

A review job is claimed under a lease. The claim is a single conditional database write, so exactly one host wins a given job however many are polling. The holder keeps the lease alive by renewing it on a timer that runs independently of review progress. A job whose lease stops being renewed counts as abandoned, however long it had been processing.

VariableWhat it doesDefaultAcceptedExample stack
REVIEW_LEASE_DURATION_SECONDSHow long a claim holds a job before its lease must be renewed12030–3600no
REVIEW_LEASE_HEARTBEAT_INTERVAL_SECONDSHow often the holder renews its lease205–1200no
REVIEW_LEASE_HEARTBEAT_JITTER_FRACTIONRandom fraction of the interval each renewal is brought forward by0.20–0.5no
REVIEW_LEASE_MAX_HEARTBEAT_FAILURESConsecutive renewal failures tolerated before the holder stops working on the job31–20no
REVIEW_LEASE_CLAIM_CANDIDATE_LIMITHow many pending jobs one poll cycle considers501–500no

REVIEW_LEASE_DURATION_SECONDS must be at least three times REVIEW_LEASE_HEARTBEAT_INTERVAL_SECONDS, and startup fails when it is not. A lease that survives only one or two renewals is lost to a single slow database call, and the job is handed to another host while the first is still reviewing it.

Raise the duration where reviews run on hosts with slow or intermittent database access. A longer lease also means a dead host’s jobs wait longer before another host can take them over.

Reclaim

A job whose lease expires is taken back and offered again, not failed. Reclaim replaced the retired WORKER_STUCK_JOB_TIMEOUT_MINUTES.

Reclaims are budgeted, which bounds what a job that keeps being reclaimed can spend. Completing further files clears the consecutive count, so only a job that keeps cycling without progress exhausts its budget. A graceful release during a deploy or scale-in does not count against either budget.

VariableWhat it doesDefaultAcceptedExample stack
REVIEW_LEASE_MAX_CONSECUTIVE_RECLAIMSReclaims allowed without completing further files31–50no
REVIEW_LEASE_MAX_TOTAL_RECLAIMSReclaims allowed in total, whatever the progress121–500no
REVIEW_LEASE_RECLAIM_BACKOFF_SECONDSHow long a reclaimed job is left alone before it may be reclaimed again600–3600no
REVIEW_LEASE_MAX_RECLAIMS_PER_SWEEPHow many jobs one sweep takes back201–500no
REVIEW_LEASE_RECLAIM_SWEEP_INTERVAL_SECONDSSeconds between reclaim sweeps305–3600no
REVIEW_LEASE_PUBLICATION_TIMEOUT_MINUTESHow long publication may run before the job counts as stuck301–720no
REVIEW_LEASE_MAX_REVIEW_DURATION_MINUTESHow long one execution of a review may run before it is failed1805–1440no

A job that exhausts its reclaim budget is failed with a reason naming the lease loss, so it reads differently from a review that failed for its own reasons.

A review that is publishing its comments is not reclaimable, however long its lease has been gone, because reclaiming it mid-publication posts the same comments twice. Publication has its own longer timeout. A publication that outlives it fails the job instead of retrying it, because some comments may already be posted.

The reclaim rules above deal with a holder that stopped renewing. A holder that keeps renewing is never taken off its job, however long it takes, so REVIEW_LEASE_MAX_REVIEW_DURATION_MINUTES caps one execution: past it the next renewal is refused and the job is failed with that reason. The cap counts from when the attempt started processing, so a reclaimed job gets the full allowance again. The reclaim budgets above bound how often that can repeat.

The check happens when a renewal is attempted, so an execution can overrun the cap by up to one REVIEW_LEASE_HEARTBEAT_INTERVAL_SECONDS before it stops. A review that finishes inside that window publishes normally. The refused renewal cancels the token the review runs under, so the review is interrupted wherever it had got to, which can be part-way through posting comments. The job ends as failed, and comments it had already posted stay on the pull request. REVIEW_LEASE_PUBLICATION_TIMEOUT_MINUTES does not prevent this; it governs how long a job may sit in publication before a sweep treats it as stuck. Set the cap well above how long a review of your largest pull requests takes.

A platform administrator can set a shorter maximum review duration for one tenant. A review that reaches it starts no further files and ends as completed, with a summary of the files it has reviewed - see tenant review limits.

Review workspace

VariableWhat it doesDefaultAcceptedExample stack
REVIEW_WORKSPACE_ROOT_PATHWhere repository mirrors and per-review workspaces are storeda directory under the service account’s local application datawritable directory pathno
REVIEW_WORKSPACE_MAX_CACHE_SIZE_MEGABYTESSize the mirror eviction sweep works towards. Mirrors held by a running review are skipped, so the total can exceed it while those reviews run; per-review checkouts are not counted against it4096128–1048576no
REVIEW_WORKSPACE_MIRROR_RETENTION_DAYSHow long a mirror is kept after the last fetch into it. The sweep removes a mirror older than this whatever the cache holds in total, and skips one a running review holds71–365no
REVIEW_WORKSPACE_RETENTION_MINUTESHow long an unreferenced workspace directory waits before the sweep removes it. This covers what a failed preparation, an interrupted review, an interrupted release, a failed delete, or a restart leaves behind; a review that ends normally deletes its own checkout at once1801–10080no
REVIEW_WORKSPACE_MAX_CONCURRENT_PREPARATIONSHow many workspaces may be prepared at once41–128no
REVIEW_WORKSPACE_FETCH_DEPTH_POLICYHow much of a repository a mirror fetch brings downfullfull, blobless, shallowno
REVIEW_WORKSPACE_FETCH_DEPTHCommits fetched under the shallow policy. Ignored by the others, which also do not check the range2001–100000no

A repository nobody has reviewed for REVIEW_WORKSPACE_MIRROR_RETENTION_DAYS is cloned again on its next review.

One preparation fetches into a mirror and writes a checkout of the repository, so REVIEW_WORKSPACE_MAX_CONCURRENT_PREPARATIONS bounds how much of the workspace disk is written at any moment. Lower it on a small disk: running out of space during a checkout fails the review.

The fetch depth policy trades local disk against fetching over the network:

  • full fetches commits, trees and file contents. Nothing is fetched again during a review.
  • blobless fetches commits and trees and leaves file contents on the server, downloading them when something reads them. It needs a server that offers filtered fetches; Azure DevOps and GitHub both do. It saves the file contents of the revisions nothing reads, which in a repository with long history is most of them. A review still downloads the head revision it checks out, and the base-revision files it compares the target side against, as it reads them. The saving therefore grows with the history behind a repository, not with its size at one commit, and the first review after the policy changes pays for what it reads.
  • shallow fetches REVIEW_WORKSPACE_FETCH_DEPTH commits. The depth has to exceed the divergence of the pull requests being reviewed: a merge base outside the fetched history cannot be resolved and the review fails to prepare. Prefer blobless for that reason.

A policy change applies to every existing mirror on its next fetch. A mirror fetched under shallow has its boundary removed by the next fetch under full or blobless, and one fetched under blobless stops filtering under full or shallow.

Leaving blobless costs one large fetch. The widening fetch asks for the contents the filter omitted, so the first preparation after the change transfers and writes the file contents of every fetched revision, and reviews after it read locally. Expect that fetch to take as long as a fresh clone of the repository, and the mirror to grow to its unfiltered size.

Why this wants a mounted volume: review workspace.

Background intervals

VariableWhat it doesDefaultAcceptedExample stack
PR_CRAWL_INTERVAL_SECONDSHow often the crawler polls for open pull requests60clamped to at least 10no
MENTION_CRAWL_INTERVAL_SECONDSHow often the mention scan runs. Each mention configuration may also ask to be visited less often than this60clamped to at least 10no
THREAD_PASS_SCAN_INTERVAL_SECONDSHow often queued thread passes are picked up and run30clamped to at least 5no
REVIEW_ARCHIVE_PURGE_INTERVAL_SECONDSHow often the retention purge sweeps archived pull-request content3600clamped to at least 60no
LICENSE_STAGE_INTERVAL_SECONDSHow often the licensing sweep runs: it reads the license stage and logs transitions, re-observes the system profile, and evaluates the metered author allowance. 0 or a negative value disables all three900values at or below 0 disable; positive values are clamped to at least 60no
WEBHOOK_DELIVERY_IDLE_POLL_SECONDSHow often an idle installation asks for a queued webhook delivery21–300no
WEBHOOK_DELIVERY_MAX_CONCURRENCYDeliveries one replica turns into reviews at once41–32no
WEBHOOK_DELIVERY_CLAIM_SECONDSHow long one replica’s claim on a delivery is good for30030–3600no
WEBHOOK_DELIVERY_MAX_ATTEMPTSAttempts a delivery gets before it is kept as failed and no longer retried51–20no
WEBHOOK_DELIVERY_RETRY_BACKOFF_SECONDSWait before a failed delivery is eligible again301–3600no

A webhook delivery is answered as soon as it is verified and stored. A worker turns it into a review afterwards, so no provider’s delivery timeout decides whether a review happens. A backlog is drained without waiting; WEBHOOK_DELIVERY_IDLE_POLL_SECONDS governs only how often an empty queue is asked. Raise WEBHOOK_DELIVERY_CLAIM_SECONDS above the slowest intake a large pull request can take on your providers, because a delivery held longer than that is assumed abandoned and given to another replica.

Raise WEBHOOK_DELIVERY_MAX_CONCURRENCY when reviews are waiting while runners sit idle. Turning a delivery into a review takes seconds, so a replica working one at a time creates roughly one job every few seconds however much execution capacity is available. Measured against a three-runner fleet with six slots, serial intake held the fleet to four slots at peak. The provider’s rate limit is the ceiling, because each delivery reads a pull request from the provider that sent it.

Crawling and @-mention scanning need a commercial license (editions); the purge sweep does not. What the purge deletes: what ProPR stores.

LICENSE_STAGE_INTERVAL_SECONDS sets how often the licensing sweep runs. Each run logs license stage changes, re-reads the system profile, and checks the monthly author count. Switching it off changes nothing about what the license allows, because every capability check reads the license directly. It stops the profile and author checks from running.

Read by the API to reach ProCursor, and by ProCursor to reach the API.

VariableWhat it doesDefaultAcceptedExample stack
PROCURSOR_REMOTE_MODEproprManagedRemote to use the service, disabled to run without itinferred: remote when a service base URL and shared key are both set, otherwise disabledproprManagedRemote, disabledyes
PROCURSOR_SERVICE_BASE_URLInternal base URL the API calls ProCursor onnoneabsolute URLyes
PROCURSOR_HEALTH_ENDPOINTPath the API probes for ProCursor’s health/healthzpathno
PROCURSOR_REQUEST_TIMEOUT_SECONDSTimeout budget for calls between the two services301–600no
PROCURSOR_RUNTIME_CONFIG_TTL_SECONDSHow long ProCursor caches the runtime configuration it fetches from the API3001 or moreno

ProCursor indexing and queries

Read by the ProCursor service.

VariableWhat it doesDefaultAcceptedExample stack
PROCURSOR_MAX_INDEX_CONCURRENCYConcurrent indexing jobs21–32no
PROCURSOR_MAX_QUERY_RESULTSResults returned per knowledge or symbol query51–20no
PROCURSOR_MAX_SOURCES_PER_QUERYSources scanned for one query201–50no
PROCURSOR_CHUNK_TARGET_LINESTarget chunk size, in lines, for indexed text12010–1000no
PROCURSOR_MINI_INDEX_TTL_MINUTESLifetime of a review-time mini-index overlay301–1440no
PROCURSOR_REFRESH_POLL_SECONDSHow often the indexing worker looks for work301–3600no
PROCURSOR_INDEX_JOB_LEASE_SECONDSHow long a claim holds an indexing job before its lease must be renewed30010–3600no
PROCURSOR_TEMP_WORKSPACE_RETENTION_MINUTESHow long a stale temporary indexing workspace is kept1201–1440no
PROCURSOR_EMBEDDING_DIMENSIONSExpected embedding vector width; must match the embedding model in use15361–4096no
PROCURSOR_TOKEN_USAGE_ROLLUP_POLL_SECONDSHow often ProCursor aggregates its token usage90010–86400no
PROCURSOR_TOKEN_USAGE_EVENT_RETENTION_DAYSRetention for raw ProCursor token usage events3651–3650no
PROCURSOR_TOKEN_USAGE_ROLLUP_RETENTION_DAYSRetention for aggregated ProCursor token rollups7301–3650no

The indexing worker renews the lease of a running job five times per PROCURSOR_INDEX_JOB_LEASE_SECONDS. When the lease of a job expires, for example because the ProCursor service stopped while the job ran, the worker returns the job to the queue, and after the third attempt it marks the job as failed. A job that is cancelled while it runs stops at the next renewal. When no renewal succeeds, for example because the database cannot be reached, the worker stops the job before its lease expires, so that the job does not run twice once another worker recovers it.

Review loop budgets

These bound the work one review may do, and the management UI does not expose them. Of everything on this page they move token spend the most - see control cost.

VariableWhat it doesDefaultAcceptedExample stack
AI_MAX_ITERATIONS_LOWInvestigation iterations allowed on a low-complexity file51–100yes
AI_MAX_ITERATIONS_MEDIUMSame, medium complexity101–100yes
AI_MAX_ITERATIONS_HIGHSame, high complexity201–100yes
AI_MAX_REVIEW_ITERATIONSIteration ceiling for review work that is not per-file, where no complexity tier applies201–100no
AI_MAX_FILE_REVIEW_CONCURRENCYHow many files one review works on in parallel31–10no
AI_MAX_FILE_REVIEW_RETRIESRetries for a review job with failed file passes31–10no
AI_MAX_RATE_LIMIT_RETRIESTransparent retries after a provider rate-limit response31–10no
AI_MAX_BACKOFF_SECONDSMaximum backoff between those retries305–120no
AI_FILE_BATCH_LINESLines returned per file-content read the reviewer makes10010–1000no
AI_MAX_FILE_SIZE_BYTESLargest file the reviewer may read; above it the read returns an error instead of content10485761024 or moreno

A tenant administrator can set both AI_MAX_FILE_SIZE_BYTES and AI_MAX_STRUCTURAL_PARSE_BYTES for one tenant through the tenant patch endpoint. Setting them requires the budgeting capability, which a commercial license carries; a value already stored is applied in every edition - see editions. A tenant value applies to every review that tenant’s clients run, here and on a runner. A tenant that sets neither reviews at the installation values above. The installation value of AI_MAX_STRUCTURAL_PARSE_BYTES is the ceiling: the structural analyzer is built with it, so a tenant value above it is clamped and a file over the installation value is still reported as too large to parse.

The size of a whole review is bounded per client instead, on the client’s Budget tab, and those bounds refuse a review before it starts - see what you can tune.

AI_MAX_FILE_REVIEW_CONCURRENCY needs a commercial license for parallel review execution to have any effect - see editions. Without it a review works on one file at a time, the same rule the worker applies to whole jobs. Sizing it alongside WORKER_MAX_CONCURRENT_REVIEW_JOBS: review workers.

When a provider rate-limits a call, ProPR waits for the delay that provider asked for. The delay is read from the Retry-After header, or from the error message when the provider states it there instead. AI_MAX_BACKOFF_SECONDS caps that wait and AI_MAX_RATE_LIMIT_RETRIES sets how many further attempts the call gets. While one connection is rate-limited, other calls bound for the same connection wait too, so a review working on several files at once stops sending requests the provider has already refused.

Which files land in which complexity tier is decided per review - see reviews.

Evidence judge budgets

The evidence judge requests low reasoning effort when the bundled catalog advertises it for the exact model and the selected transport supports it. Unknown or incompatible bindings use provider defaults. AI_VERIFICATION_REASONING_EFFORT sets one effort for the judge and both contradiction checks on every model. Set it only for models that accept a reasoning effort, because a model that does not accept one rejects the request, and the output ceilings include the reasoning tokens. The output ceiling includes reasoning tokens. An output-limit response, or an unusable verdict at the measured ceiling, receives one larger retry. Completed tool results remain available; the retry permits no further lookups and shares the claim’s 120-second deadline. Both attempts are recorded and charged. A retry that also exhausts its ceiling withholds the finding with judge_output_limit.

VariableWhat it doesDefaultAcceptedExample stack
AI_EVIDENCE_JUDGE_MAX_OUTPUT_TOKENSInitial reasoning-inclusive output ceiling per judge call4000512–32768no
AI_EVIDENCE_JUDGE_RETRY_MAX_OUTPUT_TOKENSOutput ceiling for the single recovery attempt8000512–32768; greater than the initial ceilingno
AI_VERIFICATION_REASONING_EFFORTReasoning effort the judge and both contradiction checks requestnonenone, low, medium, highno

Both the control plane and runner read these installation settings. A parsable value outside these bounds, or a retry ceiling no greater than the initial ceiling, fails startup. Unparsable values use the defaults. These checks apply only to the two judge budgets. Configure both ceilings within the judge model’s supported completion limit.

Finding thresholds

VariableWhat it doesDefaultAcceptedExample stack
AI_CONFIDENCE_THRESHOLDConfidence at which the reviewer stops investigating a concern700–100no
AI_CONFIDENCE_FLOOR_ERRORInactive compatibility setting; file confidence does not change finding classification800–100yes
AI_CONFIDENCE_FLOOR_WARNINGInactive compatibility setting; file confidence does not change finding classification600–100yes
AI_QUALITY_FILTER_THRESHOLDTotal comment count across all files below which the cross-file quality pass is skipped201–500yes

A finding that clears these thresholds still goes through the publication gate - see why a finding did not get posted.

The marker on posted comments

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_AI_GENERATED_MARKERSentence ProPR ends every comment it posts with, stating that the content was generated by AIGenerated by ProPR, an AI code reviewer.up to 200 characters on one line, without <, >, &, ", *, _, `, [, ] or \, and not starting with / or #yes

The marker is the last line of every summary, inline finding and reply ProPR posts, on all four hosts. The accepted characters are what a host is guaranteed to render as written. GitLab encodes the comment body, and a leading slash there starts a quick action. ProPR wraps the marker in Markdown emphasis, and each refused character is Markdown syntax a host can render as a link, a code span or a heading in some position, not one that always does: [ needs a following (url) to form a link, and # starts a heading only at the beginning of a line. Validation runs once against the configured wording and cannot know where in the finished comment the marker will land, so it refuses the character regardless of position. A value outside the accepted set stops startup with an error naming the variable. The wording applies to the whole installation, not to one client.

Thread memory

VariableWhat it doesDefaultAcceptedExample stack
AI_MEMORY_TOP_NPast resolutions retrieved per file review31–20yes
AI_MEMORY_MIN_SIMILARITYMinimum similarity for a past resolution to be considered0.800.0–1.0yes
AI_MEMORY_EMBEDDING_DIMENSIONSEmbedding width; must match the model bound to the embedding purpose153664–4096yes
AI_POSTED_FINDING_MIN_SIMILARITYMinimum similarity for a finding to count as a duplicate of one already posted on the pull request0.850.0–1.0yes

The embedding model itself is configured per client, not here - see purposes.

Code-structure tools

The AI_ENABLE_* switches turn these tools off and fall back to simpler behaviour. The budgets bound how much work one lookup may do before it returns what it has, marked truncated.

VariableWhat it doesDefaultAcceptedExample stack
AI_ENABLE_STRUCTURAL_BOUNDARY_RESOLUTIONUse the structural analyzer to pick context boundaries instead of a line heuristictruetrue, falseno
AI_STRUCTURAL_PARSE_TIMEOUT_MSPer-file budget for that analysis before it falls back to the heuristic20010–5000no
AI_MAX_STRUCTURAL_PARSE_BYTESLargest file it will parse10485761024–5242880no
AI_ENABLE_STRUCTURAL_REFERENCE_TOOLSRegister the cross-file reference and definition lookups and the caller-evidence feedtruetrue, falseno
AI_MAX_REFERENCE_CANDIDATE_FILESFiles scanned per reference lookup2001–2000no
AI_MAX_REFERENCE_RESULTSConfirmed sites returned per lookup501–1000no
AI_MAX_REFERENCE_RESULT_CHARSCharacter budget for one lookup result8000256–64000no
AI_REFERENCE_RESOLUTION_TIMEOUT_MSWall-clock budget for one lookup400050–30000no
AI_ENABLE_RETAINED_TOOL_EVIDENCEKeeps fetched file content across context compaction instead of dropping it, so the reviewer does not re-read content it already holds. Set to false to drop ittruetrue, falseno

Linked work items and issues

VariableWhat it doesDefaultAcceptedExample stack
AI_ENABLE_LINKED_ITEM_TOOLSWhether the on-demand linked-item lookups are available; false withholds them even from clients that include linked itemstruetrue, falseno
AI_MAX_LINKED_ITEMS_IN_CONTEXTLinked items injected into the review context before the rest are dropped51–50no
AI_MAX_LINKED_ITEM_DESCRIPTION_CHARSLength each linked item’s description is truncated to2000128–20000no
AI_MAX_LINKED_ITEM_TOOL_CALLSOn-demand linked-item lookups allowed per review60–100no
AI_MAX_LINKED_ITEM_TOOL_RESULT_CHARSCharacter budget for one such result8000256–64000no
AI_LINKED_ITEM_TOOL_TIMEOUT_MSBudget for one such lookup; on overshoot it returns empty5000100–30000no

Whether linked items are pulled in at all is a per-client setting - see what you can tune.

AI provider add-ins

VariableWhat it doesDefaultAcceptedExample stack
AI_PLUGIN_DIRECTORYDirectory holding privately built AI provider add-ins, one folder per familyplugins beside the applicationreadable directory path; a relative one resolves against the application directoryno

The AI provider families the image ships load from a directory inside the image, at a path the image fixes, whatever this is set to. This names the second directory, for a family you built yourself. A directory that is not there is reported once at startup and is not a failure, since most installations have none. The directories are read once while the host starts, so a file added or removed afterwards changes nothing until the host restarts.

Whoever can write to this directory runs code in the API host. The host loads the assemblies it finds there at startup and executes them in its own process, with its configuration, its network and its database connection. An add-in is not sandboxed and is not meant to be. Treat write access to the directory as equivalent to deploy access: mount it read-only where the platform allows it, keep it out of any volume a review or a webhook can write to, and check a binary before you put it there.

How to install a family and what the host does with one it cannot load: installing an AI provider add-in.

Egress and stored content

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_ALLOW_PRIVATE_EGRESSPermit outbound calls to private, loopback and link-local addresses, for AI endpoints and source-control hosts alikefalsetrue, falseno
AI_CAPTURE_REASONING_IN_PROTOCOLRecord the model’s reasoning into the review protocol; it can contain verbatim source excerptstruetrue, falseno

AI_ALLOW_PRIVATE_EGRESS is the earlier name of the same setting and is still read, so an installation that carries it keeps working. Where both are set, MEISTER_ALLOW_PRIVATE_EGRESS decides.

What private egress does and does not permit: outbound request protection. What the captured reasoning contains, and when to turn it off: what ProPR stores. A tenant can override AI_CAPTURE_REASONING_IN_PROTOCOL for its own reviews: model reasoning in the job trace.

Platform administrator elevations

VariableWhat it doesDefaultAcceptedExample stack
MEISTER_PLATFORM_ADMIN_ELEVATION_MAX_MINUTESLongest duration, in minutes, that a platform administrator can request for one elevation into the content of a tenant2401–1440no

What an elevation grants and what ProPR records about it: platform administrator access to tenant content.

A process-wide Azure credential

VariableWhat it doesDefaultAcceptedExample stack
AZURE_TENANT_IDMicrosoft Entra tenant of the service principal, or of the federated workload identitynone-no
AZURE_CLIENT_IDApplication ID of the service principal, or of the user-assigned managed identitynone-no
AZURE_CLIENT_SECRETIts client secretnone-no

All three together supply one Azure service principal to the backend process. That credential serves two unrelated purposes: Azure DevOps operations for a client that has no connection of its own - see global Azure fallback - and Azure-hosted AI endpoints configured with Azure Identity instead of a key. The key ring is wrapped with it where the Azure Key Vault protector is configured - see the Azure Key Vault protector.

Set AZURE_CLIENT_SECRET without both ids and the service does not start, naming the id that is missing. A secret is usable only as part of a service principal, and the ambient credential would otherwise authenticate as an identity you did not choose.

Every other combination uses the ambient Azure credential: a managed identity, a federated workload identity, an Azure CLI login, or whatever else the host offers. The two ids keep the meanings Azure gives them there. AZURE_CLIENT_ID with no secret names the user-assigned managed identity to authenticate as. AZURE_TENANT_ID and AZURE_CLIENT_ID with no secret are the workload identity pair, which the ambient credential reads together with the federated token file its own variable points at.

Code Insights

Client collection policy and the commercial Code Insights capability gate analytics. These variables control score coverage, background work and retention; the example stack does not forward them by default.

VariableWhat it doesDefaultAccepted
CODE_INSIGHTS_MIN_COVERAGE_PERCENTAGEAdvisory percentage of covered source pull requests for range diagnostics80Integer, clamped to 0–100
CODE_INSIGHTS_MIN_COVERED_PULL_REQUESTSAdvisory covered source pull requests for range diagnostics1Integer, floored at 1
CODE_INSIGHTS_CLASSIFICATION_INTERVAL_SECONDSInterval between classification backlog sweeps60Integer, floored at 10 seconds
CODE_INSIGHTS_CATCHUP_INTERVAL_SECONDSInterval between projection, seal and provider-evidence catch-up sweeps21600Integer, floored at 600 seconds
CODE_INSIGHTS_BACKFILL_MAX_JOBSMaximum jobs processed in one catch-up sweep50Integer, floored at 1
CODE_INSIGHTS_PROVIDER_EVIDENCE_BACKFILL_MAX_PULL_REQUESTSMaximum retained pull requests refreshed from provider evidence per catch-up sweep25Integer, clamped to 1–250
CODE_INSIGHTS_PROVIDER_EVIDENCE_SWEEP_BUDGET_SECONDSTime budget for one provider-evidence sweep300Integer, clamped to 10–600 seconds
CODE_INSIGHTS_PROVIDER_EVIDENCE_LEASE_MINUTESMaximum duration of a historical observation claim15Integer, clamped to 1–60 minutes
CODE_INSIGHTS_MODEL_EVIDENCE_RETRY_COOLDOWN_MINUTESDelay between bounded local classification retries for one captured model source5Integer, clamped to 1–1440 minutes
CODE_INSIGHTS_PROVIDER_EVIDENCE_RETRY_COOLDOWN_MINUTESDelay before an unavailable, retryable historical observation is eligible again360Integer, clamped to 10–1440 minutes
CODE_INSIGHTS_PURGE_INTERVAL_SECONDSInterval between source and saved-report expiration sweeps3600Integer, floored at 60 seconds
CODE_INSIGHTS_RETENTION_DAYSOrdinary collected source-data retention365Integer, floored at 1 day
CODE_INSIGHTS_REPORT_RETENTION_DAYSIndependent immutable saved-report retention365Integer, floored at 1 day

Source expiration preserves reports until their independent expiry. Collection opt-out stops new collection and retains authorized reports until expiry or deletion. Client collection-data deletion and client deletion remove complete reports containing that client’s data, including mixed-client reports. Provider refresh runs after projection and sealing, with its own batch, time and lease limits. Retryable failures use the configured cooldown. Missing usable connections, source coordinates or provider identity, unsupported historical collection and missing observer dependencies pause automatic retries until repair and explicit requeue. Generic provider failures remain retryable when retained evidence cannot establish their cause. Collection-disabled clients are not claimed.

Requeue requires tenant administration and the Code Insights capability. It rejects an active lease or disabled client collection and queues a subsequent bounded sweep without collecting evidence in the request. It preserves retained observations and retention timestamps. Shortening the catch-up interval does not shorten a retryable failure’s cooldown.

Each provider-evidence sweep logs attempted, observed, unavailable and disabled totals. Failures log the pull request identity, bounded failure reason, failing stage and recovery disposition. Unexpected failures include the exception type and numeric diagnostic code without provider messages or payloads.

Coverage settings provide advisory warnings for current range queries. Scores remain available below these minimums when covered evidence supports the calculation. Their effective values are captured with saved reports; changing them does not recalculate a capture. Recall and F1 require at least one covered source. Zero settled misses does not impose an additional sample requirement. Empty denominators remain unavailable. Reviewer performance describes live score ranges and saved reports.

Observability

VariableWhat it doesDefaultAcceptedExample stack
OTLP_ENDPOINTOTLP collector to export traces tonone - no trace pipeline is built at all, so spans are never assembledabsolute URLno
OTEL_EXPORTER_OTLP_ENDPOINTThe same collector, under the name the OpenTelemetry specification gives it. Read when OTLP_ENDPOINT is unset or empty. A managed OpenTelemetry agent injects this namenoneabsolute URLno
TELEMETRY_HTTP_CLIENT_TRACESWhich outbound requests become spans. foreground skips unattended work: crawl cycles, mention scans and health probesforegroundforeground, all, offno
TELEMETRY_TRACE_SAMPLE_RATIOHead-sampling ratio applied to the traces that survive the filters1.0 - no sampler is installed, which leaves the standard OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG in charge0.0 to 1.0, clamped into rangeno
TELEMETRY_TRACE_IGNORED_PATHSRequest path prefixes that are never traced, inbound or outbound/healthz,/livez,/metricscomma-separated path prefixes, leading / optionalno
LOKI_URLGrafana Loki instance to ship logs tonone - logs go to stdout onlyabsolute URLpinned
ASPNETCORE_ENVIRONMENTThe runtime environment nameProduction when unsetProduction, Development, or your own namepinned

The three TELEMETRY_ variables do something only while an OTLP endpoint is configured, under either name. None of them affect /metrics, which keeps counting every request. An unrecognised value is not an error: the trace mode falls back to foreground and an unparseable ratio to 1.0, so a typo silently gets you the default and the service still starts. Why you would change them: trace volume.

Development serves the API documentation UI - see the API reference - and relaxes the outbound AI egress checks - see outbound request protection. Use Production outside development. Where traces and logs end up: observability.

An optional Azure OpenAI instruction evaluator

VariableWhat it doesDefaultAcceptedExample stack
AI_EVALUATOR_ENDPOINTAzure OpenAI endpoint for the repository-instruction relevance evaluatornoneAzure OpenAI endpoint URLno
AI_EVALUATOR_DEPLOYMENTDeployment name to call on itnone-no
AI_API_KEYKey for that endpoint; omit to authenticate with the host’s ambient Azure credential insteadnone-no

This evaluator judges which of a repository’s instruction files apply to a diff. It is registered only when the endpoint and the deployment are both set, and it calls Azure OpenAI directly. It does not go through the per-client AI connections, so a client’s provider configuration does not control it. Leave all three unset unless you want it.

AI_EVALUATOR_ENDPOINT follows the address rule every AI endpoint follows: https, and no private, loopback or link-local address without MEISTER_ALLOW_PRIVATE_EGRESS - see outbound request protection. An address this installation refuses stops the service from starting, with the variable named. AI_API_KEY travels in a request header, so a plain http endpoint would put it on the wire unencrypted.

Discussion synchronization

VariableWhat it doesDefaultAcceptedExample stack
DISCUSSION_RECONCILIATION_INTERVAL_SECONDSFull discussion reconciliation cadence, independent of PR discovery300integer seconds, clamped to at least 10no
CRAWL_GLOBAL_CONCURRENCYConcurrent crawl and discussion operations across the process8integer, clamped to at least 1no
CRAWL_PROVIDER_HOST_CONCURRENCYConcurrent operations for one provider and host4integer, clamped to at least 1no
CRAWL_CLIENT_PROVIDER_HOST_CONCURRENCYConcurrent operations for one client, provider and host1integer, clamped to at least 1no
CRAWL_OPERATION_TIMEOUT_SECONDSDeadline for an individual pull-request synchronization operation90integer seconds, clamped to at least 10no

Authenticated webhook notifications make affected discussions eligible immediately. Full reconciliation recovers missed notifications and providers with incomplete event coverage. See discussion synchronization for retries, retention and diagnostics.