← All docs

Configuration Reference

Every MERGEN_* environment variable the server reads, classified by who is qualified to decide its value. This reference is generated from the same registry the build enforces; an unregistered setting is a red build, so this page cannot silently fall out of date.

One invariant governs everything here: no setting below can make the gate approve something it would otherwise have blocked, except the explicitly guarded settings, which exist for narrow recovery/migration cases and are surfaced loudly wherever they are active.

Organization policy

Settings a security admin decides: approval rules, retention, quorum, module switches. These are what teams buy Mergen to customize. None of them can weaken a gate verdict.

MERGEN_AUTOPILOT

type: boolean · default: false · module: autopilot

Enable autonomous incident fix execution (Gate B). Off by default; shadow mode is forced on first enable unless explicitly disabled.

MERGEN_AUTOPILOT_LEVEL

type: string · default: unset · module: autopilot

Autopilot autonomy level cap.

MERGEN_SHADOW_MODE

type: boolean · default: unset · module: autopilot

Dry-run the AUTOPILOT (Gate B): diagnose but never execute fixes. Defaults to true when autopilot is first enabled.

MERGEN_BLOCK_BYPASS

type: boolean · default: true

Lets an operator, never the agent, self-issue a single-use terminal token to re-run a BLOCK from an editable rule. This is a recovery valve, not a HOLD-style approval request: nothing is sent to the agent, no one is asked to approve anything. false = every BLOCK is final (stricter). Immutable/hard-safety rules are never bypassable regardless.

MERGEN_BYPASS_TTL_MINUTES

type: int · default: 10

Minutes an operator-issued BLOCK bypass token stays valid before it expires (clamped 1-1440). The window does not extend on use; the re-run must land within it.

MERGEN_APPROVAL_REUSE_MINUTES

type: int · default: 10

Minutes a HITL hold approval is honored for exactly one re-run of that command (clamped 1-1440). Single-use regardless of the window length; see MERGEN_APPROVAL_REUSE_NORMALIZE for how "that command" is matched.

MERGEN_APPROVAL_REUSE_NORMALIZE

type: boolean · default: true

Within the reuse window, also honor an approval when only a leading cd <dir> && differs, and/or leading VAR=value env-var assignments that carry the SAME value on both sides (see commandsMatchForReuse in approval-history.ts): a value that changed, or a variable newly introduced on the retry, still requires a fresh approval. The executable and arguments underneath must always be byte-identical. false reverts to literal exact-string matching only.

MERGEN_APPROVAL_GRANTS

type: boolean · default: true

Allow a human, at HOLD-approval time, to also mint a task-scoped grant covering a narrow, deterministically-matched set of follow-up commands (same exact command / same binary subcommand / same binary, always same workspace) for a bounded window + use-count. Grant relief is HOLD→PASS only: consulted solely inside holdToolCall, so it can never touch a BLOCK or alter a PASS. false disables minting and matching entirely; the single-use MERGEN_APPROVAL_REUSE path is unaffected.

MERGEN_APPROVAL_GRANT_MAX_MINUTES

type: int · default: 15

Ceiling (and default) for a task-scoped approval grant's lifetime in minutes (clamped 1-120). A grant also ends when its use-count is exhausted or a human revokes it.

MERGEN_APPROVAL_GRANT_MAX_USES

type: int · default: 5

Ceiling (and default) for how many in-scope commands a single task-scoped approval grant will cover before it is spent (clamped 1-50). Bounds a runaway retry loop to the count rather than the whole time window.

MERGEN_SECRET_GRACE_PERIOD_MINUTES

type: int · default: 5

Minutes the previous ~/.mergen/secret value is still accepted for request authentication after mergen-server rotate-secret (clamped 0-1440), giving in-flight clients (an open dashboard tab, a mid-flight OAuth callback) time to pick up the new value before the old one stops working. 0 disables the grace period: rotation takes effect immediately. See sensor/secret-manager.ts.

MERGEN_CORPUS_HOLD

type: boolean · default: false · module: override-corpus

Opt-in per-command override-corpus lookup at Gate A: a command matching a prior override/incident escalates to HOLD with a cited reason. HOLD-only, never BLOCK/PASS.

MERGEN_AUTO_CORPUS_PROPOSE

type: boolean · default: true · module: policy-suggester

Every 6h, stage repeatedly-overridden corpus patterns as HOLD-only policy proposals for one-click approval. Never auto-activates, never proposes a BLOCK.

MERGEN_EDIT_GATE_MODE

type: enum · values: on · off · default: on · module: edit-gate

Kill switch for the Edit-Time Change Gate (Claude Code, GitHub Copilot CLI and Windsurf pre-write hooks, plus Cursor/VS Code self-heal). on (default) runs the gate and each detector acts at its own rung from MERGEN_EDIT_GATE_DETECTOR_MODES. off skips detection entirely: no findings computed, every write passes. This setting is NOT a posture: it cannot make a detector block, hold or stay quiet, because every detector's rung is stated explicitly in the shipped ladder. The legacy values shadow and enforce are still accepted and both mean on, each with a startup notice: an install on enforce becomes WEAKER (the paired-diff detectors hold rather than block, which is what they were measured for) and an install on shadow becomes STRONGER (it was suppressing the whole gate; set off for that).

MERGEN_EDIT_GATE_DETECTOR_MODES

type: string · default: secret-introduction:enforce,auth-bypass:hold,ci-check-removal:hold,test-deletion:hold,sensitive-config:hold,permission-expansion:shadow,placeholder-implementation:shadow,governance-tamper:hold,supply-chain:hold,unsafe-code:hold,infra-exposure:hold,write-location:hold · module: edit-gate

Each edit-gate detector's rung, as <detector-id>:shadow|hold|enforce pairs (comma or space separated). The three values are a promotion ladder, not a preference: shadow computes and logs findings but never interrupts; hold means a human answers before the write lands, on the same HITL machinery the command path uses; enforce blocks outright and is reserved for evidence as strong as structural secrets outside test paths. What you write MERGES OVER the shipped ladder: naming one detector says nothing about the others, so a one-pair value changes one rung and leaves the rest alone. Every detector has an explicit shipped rung (see SHIPPED_DETECTOR_LADDER in edit-gate.ts for the replay evidence behind each): secret-introduction enforces; auth-bypass, ci-check-removal and test-deletion hold, because they are paired-diff detectors that cannot tell a moved check from a removed one when an Edit call carries only half a refactor; governance-tamper holds, because an agent changing the hooks, state or MERGEN_* settings that govern it is a question for a human; sensitive-config, supply-chain, unsafe-code and infra-exposure hold: they are warn-only, so by default they only log, and the hold rung makes a change control you author for their risks take effect; permission-expansion and placeholder-implementation stage in shadow, which also ignores controls authored for their risks, because at their volume a Hold would interrupt most commits. Resolution: MERGEN_EDIT_GATE_MODE=off wins over everything (the gate does not run); otherwise a detector takes your entry if you wrote one, else its shipped rung. An unknown detector id or mode word is ignored, leaving that detector on its shipped rung rather than silently disabling it.

MERGEN_PROTECTED_POLICY

type: enum · values: off · dev · enforce · default: off · module: policy-engine

Privilege-separated policy integrity: load the enterprise policy from a system, non-user-writable path (MERGEN_PROTECTED_POLICY_PATH) and verify, BEFORE activation, that it is owned by root/a system account, not agent-writable, signed with an env-only key (MERGEN_POLICY_SIGNING_SECRET, never the on-disk fallback), and HMAC-valid. off (default) uses ~/.mergen/enterprise-policy.json exactly as before. enforce refuses any policy failing those checks and falls back to the built-in DEFAULT policy (never a policy it could not verify). dev consults the protected path but on any failure logs and falls back to the user policy, the developer escape hatch (also implied by a development/test NODE_ENV). Protects the policy's INTEGRITY only; the gate PROCESS is still same-uid; that boundary is Piece B.

MERGEN_PROTECTED_POLICY_PATH

type: string · default: `` · module: policy-engine

Overrides the per-OS system location the protected policy (and its .sig sidecar) is read from when MERGEN_PROTECTED_POLICY is dev/enforce. Default (empty) resolves per-OS: /etc/mergen/enterprise-policy.json (Linux), /Library/Application Support/Mergen/enterprise-policy.json (macOS), %ProgramData%\Mergen\enterprise-policy.json (Windows). The runtime only reads and verifies this path; provisioning it (as root, via writeProtectedPolicy or an installer) is a privileged action.

MERGEN_GATED_CLIENT_MODE

type: enum · values: off · shadow · enforce · authoritative · default: off · module: policy-engine

L2→L0 daemon consultation from gate-check. off (default): the in-process HTTP gate is the only decision path. shadow: also consult the privileged mergen-gated daemon and RECORD its verdict + agreement/divergence vs the in-process outcome; the in-process outcome stays authoritative, nothing changes. enforce (additive stricter-wins): a CLEAN L0 BLOCK tightens a non-block outcome to BLOCK; L0 never downgrades, and an unreachable/untrusted/unauthorized L0 is NO OPINION (never blocks; enforce does not make the gate depend on daemon availability). authoritative: L0 is REQUIRED, with the same BLOCK-tightening as enforce, PLUS an unreachable/untrusted/unauthorized L0 → fail-closed BLOCK (governed by MERGEN_GATE_AVAILABILITY_MODE; fail-open keeps L2). Use only where the mergen-gated daemon is deployed + supervised (Piece B), since it makes execution depend on daemon availability by design. (PASS→HOLD tightening on a clean L0 hold is the authoritative slice-2 follow-on; it needs the server-side HITL lifecycle; §8.3.) All non-off modes record divergence like shadow.

MERGEN_RISK_HOLD_THRESHOLD

type: float · default: unset

Opt-in risk-score escalation: a passing call with composite risk at/above this threshold escalates PASS→HOLD. Unset = off.

MERGEN_SHELL_GATE_MODE

type: enum · values: off · advise · enforce · default: advise

Mode baked into the snippet mergen shell-init <zsh|bash|fish> emits. Routes every command typed at an interactive prompt through the same gate-check entrypoint and policy engine the IDE Bash PreToolUse hook uses (no separate shell command filter). advise (default) shows the BLOCK/HOLD banner but the command still runs. enforce (zsh only, an accept-line ZLE widget) keeps a held/denied line in the buffer, unexecuted; bash/fish stay advise regardless. off makes an installed hook inert. Read only by shell-init at snippet-generation time; never consulted by Gate A itself.

MERGEN_EPHEMERAL_WRITE_TRUST

type: boolean · default: true · module: script-trust

Ephemeral, content-bound script trust (gated-write-ledger.ts): a script whose EXACT bytes the edit gate analyzed moments earlier (at the same path, in the same workspace, producing ZERO findings) runs without a fresh script-trust HOLD. Closes the most common script_trust_hold shape in an agent session: the agent writes a throwaway script through its editor tool (which Mergen inspects in full), then gets interrupted about the same bytes a second later, an interruption carrying no new information. Trust is bound to the content hash, so one byte of drift revokes it with no explicit invalidation step; it is workspace-scoped (failing closed on an unknown workspace either side), held only in the running process's memory, dropped entirely on restart, and never persisted into trusted-scripts.json or merged into a stored capability envelope. Recorded on zero findings rather than 'was not blocked', so it stays meaningful under the default MERGEN_EDIT_GATE_MODE=shadow, where findings are computed but never enforced. Location grants nothing: a script Mergen never saw written is untrusted wherever it lives. Set false to restore the previous behavior, where such a script always holds.

MERGEN_EPHEMERAL_WRITE_TRUST_TTL_HOURS

type: int · default: 24 · module: script-trust

How long ephemeral write trust (MERGEN_EPHEMERAL_WRITE_TRUST) lasts, in hours since the bytes were last used — each use extends it while the file stays byte-identical. Clamped to 1–168. It was a fixed hour from the write until 2026-10-02, and that hour was most of the edit→test friction: 2,041 of 2,042 lookups that found the same bytes Mergen had cleared missed only because it had passed, since uncommitted work routinely sits for hours. The window was never the safety boundary: trust is keyed to the exact content (one changed byte revokes it), and whether the bytes may run — capability added over the write-time git HEAD — is re-checked on every use. Still in memory only (a restart forgets) and one workspace only. Set 1 for the old behaviour.

MERGEN_HEREDOC_PREVIEW

type: boolean · default: true · module: script-trust

Gate an inline file-editing script by its effect instead of holding it (heredoc-preview.ts). For a Claude Code Bash call shaped [cd <dir> &&] python3 - <<'EOF' … EOF [| tail], optionally followed by commands that only read (grep, wc, ls, git diff/status/log; never npm/npx/node/sed), whose capability envelope is file writes only, Mergen runs the script under macOS Seatbelt with the network and every real file write denied (its writes go to a private overlay), puts each resulting file diff through the edit gate an Edit call gets, and on a clean PASS has the hook replace the heredoc with apply-preview <id>, which writes exactly the reviewed bytes and replays the script's output. The replacement carries no permission decision, so Claude Code's own permission rules still judge it. Any finding, preview failure or other shape keeps today's script-trust HOLD. Only in permission modes acceptEdits, auto, dontAsk and bypassPermissions; other IDEs and shells are unaffected. macOS only. Set false to hold every such heredoc as before.

MERGEN_SCRIPT_TRUST_WINDOW_SECONDS

type: int · default: 30 · module: script-trust

Script-wrapping evasion guard: scripts modified within this window are auto-held. 0 disables only the freshness check (git-tracked + in-workspace checks stay).

MERGEN_OBSERVATION_WINDOW_DAYS

type: int · default: 14

Length of the first-install gate observation window (Gate A evaluates and logs every call but editable-rule blocks/holds pass through; immutable hard-safety rules still enforce from minute one). Integer days, clamped 1-90; an out-of-range or non-integer value refuses to start rather than silently reverting; below 3 boots with a warning (too little traffic to build trust). Enforcement date is always startedAt + this value, recomputed live, so changing it mid-window shifts the date deterministically instead of extending from "now". MERGEN_POLICY_DRY_RUN=false skips observation entirely regardless of this value; POST /onboarding/promote-enforcement ends it early.

MERGEN_GATE_RECOVERY_COMMANDS

type: csv · default: unset

Additive, exact-match-only extension of the built-in recovery allowlist gate-check exempts from the network round-trip. Entries with shell metacharacters are rejected.

MERGEN_GATE_AUTOSTART

type: boolean · default: true

gate-check auto-starts a missing workspace server (clean unreachable only, never a wedged one) and evaluates the command against the fresh gate. Unavailability then follows MERGEN_GATE_AVAILABILITY_MODE (fail-open by default ⇒ allow; fail-closed ⇒ block). Under fail-open, autostart is what lets a down gate recover to actually evaluate, so leaving it on is the availability-preserving default; a deliberate mergen-server stop pauses auto-start until an explicit start.

MERGEN_AUTO_UPDATE

type: boolean · default: true

Install a newer mergen-server release by itself (2026-10-29): after the daily update check, an npm global install downloads the release, verifies its Ed25519 signature against the keys pinned in the running release (never the registry's word alone), installs that verified tarball, and restarts the router only while no approval is waiting and never after a person's mergen-server stop. Inactive until a release-signing key is pinned. Homebrew and source installs keep the update notice. Set false to update by hand (mergen update).

MERGEN_HOOK_SELF_HEAL

type: boolean · default: true

On each server boot, repoint an already-installed Claude Code PreToolUse hook whose baked-in CLI path has drifted (Node/nvm upgrade, package-manager switch, moved global prefix) back at this install. Only repairs a hook the user already opted into (never creates one) and never changes a gate verdict. Skipped in cloud mode. Set false to leave hook maintenance to mergen-server setup/doctor --fix only.

MERGEN_HOOK_INLINE_ASK

type: enum · values: off · on · default: off

When on, an interactive Bash-gate HOLD is surfaced as an inline yes/no prompt where the developer already is, instead of an exit-2 "HELD: approve elsewhere, then re-run" banner: no token, no mergen approvals, no retry. Surface-agnostic mechanism, two renderings: a Claude Code PreToolUse hook returns hookSpecificOutput.permissionDecision="ask" (with a PostToolUse gate-check --confirm reconciling it); an interactive terminal caller (mergen shell-init's zsh enforce widget, or a manual gate-check -- <cmd>) prompts on the tty and exits 0 (approve → run) or 2 (deny → keep/block). Never weakens enforcement on a no-human path: for Claude Code it is only emitted in a permission_mode that actually prompts (default/acceptEdits/plan, never bypassPermissions / --dangerously-skip-permissions); for the tty path a no-answer/EOF/timeout is a deny. A hold delegated this way does not suspend on a Promise, is excluded from the one-hold-per-session concurrent-hold guard (removing that cascade), carries a short TTL, and is reconciled to approved on confirm or swept to auto_denied if never confirmed. Does not affect Edit/Write holds (edit gate has no HOLD tier), Windsurf's pre_run_command (no verified inline-prompt channel; stays on HELD), mergen exec (already resumes in place), or Slack/webhook delivery.

MERGEN_REQUIRE_GATE_HEARTBEAT

type: boolean · default: false

Require a fresh gate heartbeat for health to report ok (stricter when true).

MERGEN_TRUSTED_HUMANS

type: csv · default: unset

Principals treated as verified humans for policy conditions.

MERGEN_ACTOR_ROLES

type: string · default: unset

Actor→role mapping consumed by policy rule role conditions.

MERGEN_HITL_QUORUM_RULES

type: csv · default: unset · module: hitl

pattern:count pairs: a hold matching a pattern requires N distinct approvers before the Promise resolves.

MERGEN_APPROVAL_ROUTING_RULES

type: csv · default: unset · module: hitl

Inline resource/repo/team-based HITL approval routing: comma-separated selector=team[:role][:count], selector one of repo:, path:, cmd:, owner:<@team>. File rules (~/.mergen/approval-routing.json) are evaluated first. Team/role require the rbacRouting entitlement.

MERGEN_LEDGER_RETENTION_DAYS

type: int · default: 90

Durable per-agent action ledger retention (every PASS/HOLD/BLOCK). Rows past the window are pruned hourly; 1M-row hard cap.

MERGEN_AUDIT_RETENTION_DAYS

type: int · default: 90

Compliance ledger (http_audit/execution_audit) retention. Rows past the window are pruned hourly; 200k-row-per-table cap. Distinct from MERGEN_LEDGER_RETENTION_DAYS, which governs the action ledger.

MERGEN_RETENTION_HOURS

type: int · default: unset

In-memory/ring-buffer event retention in hours. Distinct from MERGEN_LEDGER_RETENTION_DAYS (the durable gate-decision ledger).

MERGEN_REDACT_KEYS

type: csv · default: unset

Additional key names for the PII shield to redact (additive, stricter).

MERGEN_CAPTURE_TERMINAL_RETAIN

type: enum · values: none · metadata_only · redacted · full · default: redacted

What terminal-command text is written to the durable action ledger, AFTER the PASS/HOLD/BLOCK decision is already made, never a precondition for making one. none/metadata_only retain no command text; redacted/full both pass the command through the PII/secret redaction pass unconditionally: "full" means full operational context subject to mandatory redaction, never byte-for-byte raw capture. No findings_only mode for this category: a bare command has no sub-findings to summarize (see change-gate detector findings for where findings_only applies instead). A tampered or unsigned signed-settings file falls back to 'none' (the strictest mode), not to this default; see capture-policy.ts. NOT RETROACTIVE: changing this value only affects future ledger writes; rows already written (including under the pre-2026-07-25 unconditional-raw default) keep whatever content they were written with. There is no migration or purge tool yet.

MERGEN_CAPTURE_FILECHANGES_RETAIN

type: enum · values: none · metadata_only · findings_only · default: findings_only

What change-detector finding detail is retained alongside an edit-time gate decision, AFTER the verdict is already computed, never a precondition for computing one. none/metadata_only retain no per-finding evidence text (metadata_only keeps file paths, counts, and detector/severity categories only). findings_only (default) keeps the full per-finding shape with evidence passed through the PII/secret redaction pass unconditionally. No redacted/full mode yet for this category: those need diff-hunk-extraction machinery this slice does not build. The verdict itself is unaffected by this SETTING at every mode, and a finding's file-path token is redacted unconditionally, independent of this setting (a path can embed a secret). A tampered or unsigned signed-settings file falls back to 'none' (the strictest mode), not to this default (which is itself fairly permissive); see capture-policy.ts. NOT RETROACTIVE; see MERGEN_CAPTURE_TERMINAL_RETAIN's note above; the same caveat applies here.

MERGEN_CAPTURE_CLOUD_RETAIN

type: enum · values: local_only · metadata_only · match_local · default: local_only

Second gate applied on top of (never instead of, and never more permissive than) terminal.retain/fileChanges.retain, governing what may leave THIS MACHINE at all: Mergen's own SaaS (the live cloud-approval relay, audit-events sync, approvals sync), the self-hosted fleet-audit sync plane, and any customer-configured SIEM sink. effectiveOutboundMode = stricter(local mode, this ceiling), composed at the moment of each egress attempt, never cached from the local write. local_only (default) ceilings each category at its own most-restrictive local mode (none); metadata_only ceilings at each category's own metadata_only shape; match_local applies no additional ceiling beyond whatever the local retain mode already decided. Defaults to the strictest legal value here (unlike terminal/fileChanges's moderate defaults) because crossing the machine boundary is a materially larger trust boundary than local disk. A tampered or unsigned signed-settings file falls back to local_only (already the strictest legal value); see capture-policy.ts.

MERGEN_DOWNGRADE_GRACE_HOURS

type: int · default: 72

How long an install that has just moved from a paid plan to Free (cancellation, expiry, or failed payment) keeps full enforcement regardless of the Free limit. The usage count covers the whole billing month, so without this grace an account that had already passed 1,000 actions on its paid plan would hit the Free limit the moment it moved, and with MERGEN_FREE_CAP_MODE=passthrough enforcement would pause for the rest of the month. The Free limit applies to Free usage, not to usage already paid for. 72 hours covers a typical card retry. Separately, the Free allowance is counted from the moment of the move, not from the start of the month, so the grace ending does not put the install over the limit at once. 0 turns the grace off and keeps only that counting. A paid install that cannot reach the licensing server is never limited at all and needs neither.

MERGEN_SSO_REQUIRED

type: boolean · default: false · module: identity

Require SSO bearer auth on all mutating requests (stricter when true).

MERGEN_SSO_ALLOWED_EMAILS

type: csv · default: unset · module: identity

Allow-list of SSO principals permitted to authenticate.

MERGEN_OIDC_GROUP_ROLE_MAP

type: string · default: unset · module: idp

IDP group→Mergen RBAC role mapping.

MERGEN_SANDBOX

type: enum · values: docker · seatbelt · default: unset · module: sandbox

Sandbox mergen-server exec: docker (container) or seatbelt (macOS). Opt-in; falls back to direct host execution with a loud warning if the backend is unavailable.

MERGEN_SANDBOX_NETWORK

type: enum · values: none · bridge · default: none · module: sandbox

Sandbox network access: none (default) or bridge.

MERGEN_SANDBOX_IMAGE

type: string · default: unset · module: sandbox

Docker backend image (must contain the wrapped command's toolchain).

MERGEN_SANDBOX_EXTRA_WRITE_PATHS

type: csv · default: unset · module: sandbox

Seatbelt backend: extra absolute paths the sandboxed command may write to.

MERGEN_SANDBOX_DOCKER_RUNTIME

type: string · default: unset · module: sandbox

Alternate OCI runtime (e.g. gVisor runsc) for the Docker backend; falls back to runc with a warning if unregistered.

MERGEN_EBPF_VERIFY

type: boolean · default: unset · module: sandbox

eBPF-based execution verification (Linux): cross-checks that what ran matches what was gated.

MERGEN_SLACK_OVERRIDE_LOOP

type: boolean · default: unset · module: override-corpus

Auto-scan the incident channel every 6h for postmortem threads to build override corpus entries.

MERGEN_REQUIRE_GITHUB_WEBHOOK_SECRET

type: boolean · default: false · module: github

Reject all GitHub webhooks instead of accepting unverified ones in local diagnosis-only mode (stricter when true; autopilot/cloud always reject unverified).

MERGEN_SIEM_LEDGER_VERDICTS

type: csv · default: block,hold · module: siem

Which action-ledger verdicts to stream to the SIEM webhook. Every PASS is still recorded locally; this only governs forwarding.

MERGEN_AUTO_WATCH

type: boolean · default: true · module: process-watch

Auto-watch local processes.

MERGEN_AUTO_ATTACH_PORTS

type: csv · default: unset · module: process-watch

Ports to auto-attach the process watcher to.

MERGEN_WATCH

type: boolean · default: unset · module: process-watch

Process watch toggle.

MERGEN_DEGRADATION_WATCH

type: boolean · default: true · module: degradation-watch

Background degradation watcher: the native desktop notification fired when this runtime stays severely degraded. On by default; set to 'false' to disable. The CLI-side parity for the VS Code extension's notifyOnUnhealthy; mergen notify is the friendly front door onto it.

MERGEN_CAUSALITY_WINDOW_MIN

type: int · default: unset · module: incident-memory

Causal-analysis lookback window in minutes.

MERGEN_SEVERE_ERR_COUNT

type: int · default: unset

Error-count threshold for severe-incident classification.

MERGEN_SEVERE_WINDOW_MS

type: int · default: unset

Window for severe-incident error counting.

MERGEN_SEVERE_DURATION_MS

type: int · default: unset

Duration threshold for severe-incident classification.

MERGEN_DOWNTIME_COST_PER_HOUR

type: float · default: unset

Downtime cost input for impact-report economics.

MERGEN_REVENUE_PER_MINUTE_USD

type: float · default: unset

Revenue-per-minute input for impact-report economics.

MERGEN_TOPOLOGY_MAX_EDGE_AGE_DAYS

type: int · default: unset

Max age of service-graph edges before pruning.

MERGEN_POLICY_MERGE

type: string · default: unset · module: policy-sync

Merge behavior when syncing remote policy with local rules.

MERGEN_REQUIRE_SIGNED_ORG_POLICY

type: boolean · default: false · module: cloud-policy-sync

Refuse a My Organization policy that arrives without a valid Ed25519 signature from a key pinned in this release. Off until auth-server signs every response; a signed policy with a bad signature is refused either way.

MERGEN_GIT_SYNC

type: boolean · default: unset · module: calibration

Sync calibration data via git.

MERGEN_GIT_ADR_SYNC

type: boolean · default: unset · module: override-corpus

Scan git history + ADRs daily for operational constraints to materialize as override corpus entries.

Guarded settings

Settings that weaken enforcement by design. They exist for narrow, legitimate cases (onboarding trials, policy migration, break-glass recovery) and are impossible to miss when active: mergen-server doctor, GET /health/integrations, the startup banner, and audit surfaces all report them. Each entry below states exactly what the dangerous value does.

MERGEN_GATE_AVAILABILITY_MODE

type: enum · values: fail-closed · fail-open · default: fail-open

gate-check behavior when a decision genuinely cannot be obtained (server unreachable/timeout/stopped). Never applies to a computed BLOCK. DEFAULT fail-open (2026-09-19 product decision): a runtime that is down should not block the developer's hooks. Set fail-closed on shared machines or lower-trust-agent deployments to block when no decision can be reached. An organization-managed machine (signed into an organization, or holding its policy) fails closed whatever this says (2026-10-20), and mergen interception deploy pins fail-closed in the root-owned managed settings. A CI job (CI, GITHUB_ACTIONS, GITLAB_CI, … set) fails closed when this is unset (2026-10-31); an explicit fail-open there is honored with a warning.

⚠️ When active: fail-open: a wedged, absent, stopped, or timed-out gate ALLOWS tool calls through instead of blocking them. This is the default (availability over enforcement); an agent that can stop the runtime can bypass the gate. Set fail-closed where that tradeoff is unacceptable.

MERGEN_POLICY_DRY_RUN

type: boolean · default: false

Policy dry-run: verdicts are computed and logged but NOT enforced. For policy testing before rollout. Explicit false also vetoes the onboarding-window auto-dry-run.

⚠️ When active: policy dry-run: BLOCK/HOLD verdicts are logged but NOT enforced. Every agent action executes. Unset once policy testing is done.

MERGEN_ALLOW_UNSIGNED_POLICY

type: boolean · default: false

Accept an unsigned enterprise-policy.json (migration escape hatch). Refuses to start unless MERGEN_UNSAFE_ALLOW_UNSIGNED=true is also set.

⚠️ When active: unsigned policy accepted: anything with filesystem access (including AI agents) can rewrite live enforcement policy without detection. Unset once policy migration is complete.

Requires MERGEN_UNSAFE_ALLOW_UNSIGNED=true to also be set; refuses to activate alone.

MERGEN_UNSAFE_ALLOW_UNSIGNED

type: boolean · default: false

Confirmation flag for MERGEN_ALLOW_UNSIGNED_POLICY; meaningless alone.

⚠️ When active: MERGEN_UNSAFE_ALLOW_UNSIGNED=true is set: unsigned-policy acceptance is armed (active only with MERGEN_ALLOW_UNSIGNED_POLICY=true).

MERGEN_PANIC_BYPASS_UNTIL

type: int · default: unset

Epoch-ms until which the panic bypass suspends gate enforcement (break-glass). Also settable via file; every use is logged and time-bounded.

⚠️ When active: PANIC BYPASS ACTIVE: gate enforcement is suspended until the configured time. If this is not a live break-glass emergency, unset it now.

MERGEN_BUILD_SOURCE_MAP

type: csv · default: unset · module: script-trust

Opt-in build-artifact trust for script-trust holds. Entries are <outDir>:<srcDir>:<srcExt> (e.g. server/dist:server/src:.ts). When Gate A lets a build command run (npm run build, tsc, esbuild, node scripts/build*.mjs, ...), Mergen hashes the outDirs before it and again when it ends (the PostToolUse hook, or the next gated action). A file the build wrote whose mapped source is committed and clean — or is exactly what the edit gate analyzed at write time — is recorded by content hash, and running those exact bytes needs no fresh approval. Any other write to the artifact, by any means, changes the hash and holds as usual. Output of a build Mergen did not see (a terminal without mergen shell-init) holds once per command. Residual: a write into an outDir DURING a build window by something Gate A never sees (a background process, another repository's runtime) is indistinguishable from the build's own output. Unset by default: build artifacts get no special treatment.

⚠️ When active: build-artifact trust is active for one or more configured outDir rules: a script under a matching path runs without a fresh human approval when Mergen watched a build write those exact bytes from committed (or edit-gate-analyzed) source. Editing the artifact afterwards, by any means, revokes it. The remaining gap is a write into the outDir DURING a build by a process Mergen does not gate, such as a background job left running by an earlier command.

MERGEN_SCRIPT_TRUST_COVERAGE

type: enum · values: full · entry · default: full · module: script-trust

Which files script-trust checks before a command runs. full (default): the file named on the command line, every local file it imports (relative imports, followed transitively), and for test runners that discover their own files (vitest, jest, mocha, pytest, bun test) the test files they will run plus pytest conftests. entry: only the file named on the command line — the behaviour before 2026-09-22, when an agent's edit to an IMPORTED module (or an agent-written test run by vitest) executed without script-trust ever seeing it. One hold covers every untrusted file a command runs and one approval vouches for all of them, so full costs at most one hold per command, not one per file.

⚠️ When active: script-trust is checking only the file named on the command line: code that file imports, and test files a runner like vitest or pytest discovers on its own, execute without being checked. An agent can place unreviewed code in an imported module or a new test file and run it through a trusted entry point. Leave at full unless the extra holds are measured to be unacceptable.

MERGEN_TRUST_TEST_INVOCATIONS

type: boolean · default: false · module: script-trust

Opt-in bounded test-runner trust for script-trust holds. When set, a script invocation is trusted WITHOUT the usual freshness/git-clean requirement if ALL of: (1) it matches a recognized test-runner shape (today: only node's own --test flag), (2) the target's filename matches a conventional test-naming pattern (.test., .spec., test_*.py, *_test.py), and (3) the target's containing directory is git-tracked (has at least one other tracked file). Found live (2026-08-13, real-usage friction study of 420 script_trust_hold resolutions): the dominant addressable slice of low-worth repeat holds was ordinary TDD iteration (edit a test file, re-run it) where the freshness check correctly (by design) re-holds on every edit even though a human approving each successive edit provides negligible signal. Unset by default: a test invocation gets no special treatment and the ordinary content-hash-approval model applies unconditionally, same as any other script.

⚠️ When active: test-runner invocation trust is active: a script run via a recognized test-runner shape (currently node --test <path>) against a conventionally-named test file in a git-tracked directory can run without a fresh human approval, even when freshly edited/uncommitted. An agent with legitimate write access to an established test directory could still name a file to match the test-naming convention and have it trusted without review. Leave unset where that risk is unacceptable.

MERGEN_TRUST_EPHEMERAL_SCRIPTS

type: boolean · default: false · module: script-trust

Opt-in trust for a heredoc/stdin-piped python or node script whose content is provably free of any subprocess/exec, filesystem write, network I/O, dynamic-code-execution, cloud-SDK, or sensitive-path/credential-env-var primitive this scan recognizes; see scriptHasNoSideEffects in script-trust.ts. Found live (2026-08-18 real-usage friction audit): script_trust_hold is 63-65% of all HOLDs Mergen has ever recorded, and the single largest pattern within it is a one-off heredoc debug script with no real side effect. Deterministic keyword/pattern scan only, never a scored classifier: a hit anywhere fails closed to the unchanged HOLD behavior. Bash/sh/etc. excluded entirely (no meaningful side-effect-free subset of a shell script). Accepted residual gaps: reflection/string-concatenation evasion (getattr(os, 'sys'+'tem')) is only partially mitigated, not closed; module-aliasing evasion for the os module specifically (import os as o) is not caught (subprocess/child_process ARE caught even when aliased, since they have no legitimate bare-import use in this shape, unlike os). Unset by default: a heredoc/stdin script gets no special treatment and the ordinary human-approval-per-content-hash model applies unconditionally.

⚠️ When active: ephemeral heredoc/stdin script side-effect scan is active: a python/node heredoc with no matched dangerous primitive can run without a fresh human approval, based on a deterministic keyword scan, not a real interpreter/AST analysis. A sufficiently obfuscated reflection call, or an os import aliased to evade the os.* checks, could slip through undetected. Leave unset where that risk is unacceptable.

MERGEN_TRUST_INLINE_CAPABILITY_SUBSET

type: boolean · default: false · module: script-trust

Opt-in capability-envelope trust for heredoc/stdin scripts (inline-envelope-grants.ts). When a human approves an inline-script HOLD, the capability envelope they approved is remembered IN MEMORY for that workspace + interpreter, and a later inline script whose envelope is a SUBSET of it runs without a fresh hold. Closes the structural asymmetry that teaches agents a syntactic workaround: a script written to a FILE and approved once already keeps running through later edits within its envelope (evaluateCapabilityDiffTrust), while a heredoc is keyed by exact content hash, so every one-off script is a first sighting and holds. The cheaper route is therefore cat > /tmp/x.py && python3 /tmp/x.py, which is no more reviewed than the heredoc it replaces: friction with no security yield. Strictly narrower than the file tier it mirrors: workspace-scoped (an approval in repo A never authorizes repo B; unknown workspace on either side fails closed), interpreter-scoped, subset-only (anything new re-holds and the hold names what is new), TTL-bounded (2h), process-memory only, never persisted to trusted-scripts.json, and dropped on restart. Cannot affect a BLOCK, and cannot grant an envelope no human approved. Unset by default: every heredoc holds per exact content hash, as before.

⚠️ When active: inline capability-envelope trust is active: after one human approval of a heredoc script in a workspace, later heredocs in that same workspace and interpreter run without a fresh hold for up to 2 hours, provided their capability envelope introduces nothing new. The envelope is a coarse keyword scan, so two scripts with very different intent (write a log file vs. delete a tree) can share the filesystem-write envelope and the second will not be reviewed. Leave unset where every inline script must be seen by a human.

MERGEN_PANIC_BYPASS_REASON

type: string · default: unset

Operator-supplied reason recorded on panic-bypass audit events.

⚠️ When active: panic-bypass reason is set (informational; MERGEN_PANIC_BYPASS_UNTIL controls activation).

MERGEN_DISABLE_LOOPBACK_TOKEN

type: boolean · default: false

Disable the loopback-token guard on local API requests (for legacy clients / containerized setups where the token file cannot be shared). Weakens local API auth to the pre-token posture.

⚠️ When active: loopback-token guard DISABLED: any local process can call the API without the per-boot token, relying only on the shared-secret guards. Intended for containerized/legacy setups; unset once the token file can be shared.

MERGEN_ZERO_RETENTION

type: boolean · default: false

VPC/regulated mode: no on-disk event retention. Deliberate privacy posture, but it also thins the local audit trail; treat as a conscious trade-off.

⚠️ When active: zero-retention mode: events are not persisted to disk. Audit reconstruction after an incident will be limited to what was forwarded (SIEM/ledger).

MERGEN_GATE_DISABLE_EVALUATE_CATEGORIES

type: csv · default: unset

Comma-separated Gate A rule categories (POLICY_RULE_CATEGORIES, enterprise-policy-engine.ts) excluded from evaluation entirely, the "Evaluate" dimension. Only rules with action: "warn" can ever carry a category, so this can never disable a hard BLOCK, sequence-threat/injection/self-protection detection, or the 5 IMMUTABLE_RULE_IDS. Unknown category tokens are ignored, never treated as "disabled."

⚠️ When active: one or more Gate A rule categories have Evaluate disabled: their WARN/HOLD rules are never checked. No BLOCK-tier rule, sequence-threat/injection/self-protection detection, or immutable hard-safety rule is ever affected by this setting. Unset for production use.

MERGEN_GATE_ENFORCEMENT_OVERRIDES

type: csv · default: unset

Comma-separated ruleId:target pairs downgrading an eligible rule's effective enforcement action, the "Enforce" dimension. target is "observe" (never affects verdict, still recorded in the audit trail) or "advise" (inform, execution continues, never a HOLD). Only ever settable for a rule whose authored action is "warn": a rule id whose action is "block" is silently ignored, never downgraded, by construction (the override map is never even consulted for it). Unknown rule ids are ignored, never treated as a match.

⚠️ When active: one or more Gate A rules have their enforcement downgraded to Observe or Advise instead of Hold. No block-tier or immutable rule is ever affected by this setting. Unset for production use.

MERGEN_FREE_CAP_MODE

type: enum · values: protect · passthrough · default: passthrough

What the gate does once a Free-plan install has used its 1,000 included protected actions for the month. "passthrough" (default): enforcement pauses until the month resets or the plan is upgraded. Every command and file edit runs without being evaluated: no policy rules, no holds for approval, and no hard-safety block, so even rm -rf / or terraform destroy is not stopped. "protect": every action is still evaluated and enforced exactly as before the limit, and the limit only adds an informational notice to the gate's response. Paid plans have no limit and are never affected. The usage count is kept in a file on this machine. If the setting cannot be read, the gate behaves as "protect".

⚠️ When active: free-cap passthrough (also the default as of 2026-09-09): once this Free install passes 1,000 protected actions in a billing period, the gate STOPS EVALUATING: every Bash command and file edit runs completely ungated (no policy rules, no HITL, no blast-radius, and no hard-safety block) until the month resets or the plan is upgraded. Set MERGEN_FREE_CAP_MODE=protect to keep full, uncapped enforcement on Free. Paid plans are unaffected either way.

MERGEN_HITL_LOCAL_APPROVAL

type: enum · values: allow · below-high-risk · deny · default: below-high-risk

Whether a HOLD may be resolved by a caller presenting only the on-disk ~/.mergen/secret. That file is readable by any process running as the developer, which on a single-uid install includes the governed agent itself, so a purely local approval is evidence that something on this machine read a file, not that a human decided anything. "below-high-risk" (default) accepts a local approval for an ordinary hold but requires an out-of-band one for a hold whose deterministic risk tier is high. "deny" requires out-of-band for every hold. "allow" is the pre-2026-09-22 behavior, where the secret alone resolved anything. Out-of-band means a signature-verified Slack action (which never reaches this check) or an x-mergen-member caller under enabled RBAC whose identity SSO verified (MERGEN_SSO_REQUIRED=true with OIDC or MERGEN_SSO_TOKEN). An unverified member header is a claim any local process can send, so RBAC without SSO does not count as a channel. BOOTSTRAP: when no out-of-band channel is configured at all, this degrades to "allow" with a loud warning rather than locking a solo developer out of their own machine — being unable to approve is worse than a weak approval, and the two prior RBAC lockout incidents are why. Recovery-command holds stay locally resolvable regardless. Never consulted by Gate A: this governs who may RESOLVE a hold, never whether one is placed.

⚠️ When active: local HITL approval is unrestricted: any process that can read ~/.mergen/secret can resolve a hold, and on a single-uid install that includes the agent being governed. Set MERGEN_HITL_LOCAL_APPROVAL=below-high-risk (default) or deny, and configure Slack or RBAC, to require an out-of-band decision.

Infrastructure

Settings an infrastructure operator decides: ports, paths, URLs, deployment context, and credential material. Changing these describes a different deployment; it does not loosen the gate.

MERGEN_GATED_SOCKET

type: string · default: `` · module: policy-engine

Overrides the canonical decision-socket path the privileged enforcement daemon (mergen-gated, Piece B) listens on and clients verify+connect to. Default (empty) resolves per-OS: /run/mergen/gated.sock (Linux), /Library/Application Support/Mergen/gated.sock (macOS), \.\pipe\mergen-gated (Windows). The socket and its directory must be owned by a privileged principal and not agent-writable; clients reject an untrusted socket and never fall back to TCP.

MERGEN_GATED_ALLOWED_UIDS

type: string · default: `` · module: policy-engine

Comma/space-separated uid allowlist of agent accounts permitted to consult the privileged enforcement daemon (mergen-gated, Piece B) once their KERNEL peer-credential (SO_PEERCRED) is verified. Root and the daemon's own uid are always allowed; every other uid must be listed here. Default (empty) is fail-closed (only root/self may consult the gate), so wiring the peer-cred read never itself widens access; the operator names the agent uid(s) explicitly. This is authorization AFTER kernel authentication, never a substitute for it.

MERGEN_GATED_ALLOWED_SIDS

type: string · default: `` · module: policy-engine

The WINDOWS (SID) counterpart of MERGEN_GATED_ALLOWED_UIDS for Piece B OS parity. Comma/space-separated Windows SID allowlist of agent accounts permitted to consult the privileged enforcement daemon (mergen-gated) once the caller's token SID is verified from the named-pipe connection. SYSTEM (S-1-5-18) and the daemon's own service SID are always allowed; every other SID must be listed here. Default (empty) is fail-closed. NOTE: the Windows enforcement daemon (Windows Service backend) is not shipped yet; this key + the gated Win32 allowlist path are onboarding groundwork prepared for it; no Windows peer is authorized until the native peer-cred read and Windows Service land. Authorization AFTER authentication, never a substitute for it.

MERGEN_PEERCRED_ADDON

type: string · default: `` · module: policy-engine

Overrides the resolved path to the compiled native peer-cred addon (native/peercred/build/Release/peercred.node) the Piece B daemon loads to read SO_PEERCRED. Default (empty) resolves relative to the loader. When the addon cannot be loaded the daemon fails CLOSED (rejects every peer) rather than authenticating without the kernel credential. Build it with npm run build:native.

MERGEN_GATE_HOOK_TIMEOUT_MS

type: int · default: ``

How long gate-check (the PreToolUse hook) waits for a decision, including a human approving a HOLD, before reporting HELD. Unset: derived from the hook timeout actually installed for the client, minus 20s for autostart, the pending-hold lookup and process start, capped at 90000: 90000 for a 120s hook (Claude Code, Copilot CLI, Cursor shell, new Codex installs), 40000 for an older 60s one (an existing trusted Codex hook is never rewritten). 20000 for Windsurf, whose hook timeout is not configurable. Setting this overrides the derivation for every client; past the installed hook timeout minus ~15s the IDE kills the hook before it can report HELD (Codex then runs the call).

MERGEN_GATE_HEARTBEAT_INTERVAL_MS

type: int · default: unset

Interval for the gate liveness heartbeat.

MERGEN_GATE_HEARTBEAT_MAX_AGE_MS

type: int · default: unset

Maximum heartbeat age before the gate is considered stale.

MERGEN_AUDIT_MAX_ROWS

type: int · default: 200000

Per-table hard cap on the compliance ledger (http_audit/execution_audit). Oldest rows past the cap are pruned even inside the retention window: this store rewrites its whole file on every flush, so the cap bounds flush cost, not just disk.

MERGEN_MAX_BLUNDERS

type: int · default: unset

Cap on stored agent-blunder records.

MERGEN_MAX_GATE_HISTORY

type: int · default: unset

Cap on stored gate decision history entries.

MERGEN_PORT

type: int · default: 3000

HTTP listen port.

MERGEN_ROUTER_PORT

type: int · default: unset

ADR-017 Phase 1 (experimental, opt-in: started only via mergen-server router, never by mergen-server start): pins the router process's canonical listen port. No default; unset means "auto-scan 3000-3010", the same range mergen-server itself defaults to; a registered default of 3000 here would make an unset value indistinguishable from an explicit pin. Distinct from MERGEN_PORT, which pins a WORKER's own port (including one the router spawns); the two must never be conflated, since a router and a worker are different process kinds.

MERGEN_BIND

type: string · default: 127.0.0.1

Bind address. Non-loopback enables team mode and requires MERGEN_SECRET + MERGEN_PUBLIC_URL.

MERGEN_WORKER_IDLE_REAP_HOURS

type: int · default: 24

ADR-017 router-spawned workers (bound to a port in worker-manager.ts's private 40000-40999 range) self-exit after this many hours with no non-health HTTP request, so an abandoned scratch/benchmark workspace's detached worker does not run forever and squat low ports in that range (found live 2026-08-15: leaked benchmark workers pushed a real workspace's worker up to :40004). "No non-health request" only means genuinely untouched; a workspace with an open VS Code panel or active CLI/hook traffic keeps resetting this on every real poll (app.ts records activity ahead of every route except literal /health), so this never reaps something actually in use. Lowered from the original 168h (2026-08-28, a second lifecycle audit): a week was tuned for the port-squatting bug alone, not for a dev machine's actual memory footprint; each idle worker independently holds a full policy engine + ledger/audit DB connections, and 168h let 17 workers (7.5GB RSS) accumulate over a single week of normal multi-workspace use. 24h still tolerates an overnight/weekday gap; a reaped worker respawns lazily and transparently on the next real request (resolveOrSpawnWorker), so this is a resource-hygiene tuning knob, not a correctness one. Never applies to a classic mergen-server instance on the legacy 3000-3010 range, or an explicit MERGEN_PORT pin outside 40000-40999; those were started by deliberate human action (mergen-server start) and are never auto-reaped. Set to 0 to disable.

MERGEN_WORKER_IDLE_REAP_MINUTES_NON_REPO

type: int · default: 30

Idle limit, in minutes, for a router-spawned worker whose workspace is NOT a git repository (or an explicit .mergen/workspace-id): one folder, one worker, since such a workspace is the folder itself. Agents run commands from many of those - per-session scratch folders, temp clones, Downloads - and each held a full worker for MERGEN_WORKER_IDLE_REAP_HOURS (24h) after its last use (2026-09-30: four such workers on one machine within an hour, ~60 MB each). A repository workspace keeps the hours setting. Same activity rule: any non-health request (hook calls, an open VS Code panel) resets it, so a folder in use is never reaped. 0 disables the shorter limit (non-repo workers then follow the hours setting).

MERGEN_FILE_READ_TIMEOUT_MS

type: int · default: 3000

Time limit for each read of a file an agent's command may run or import (script-trust, test discovery, import scanning; intelligence/bounded-read.ts). The read happens on a helper thread so a file whose content is not local - an online-only cloud file (OneDrive Files On-Demand, Dropbox, iCloud, Google Drive) or a stalled network drive - can never freeze the gate: past this limit the command holds, naming the file. A slow but working network share may need more. Never makes anything pass: a read that times out holds.

MERGEN_ROUTER_ORPHAN_CHECK_SECONDS

type: int · default: 45

How often the ADR-017 router re-checks that it is still the machine's canonical router (the pid named in DATA_DIR/router.json). A router that finds a DIFFERENT live pid recorded there self-exits at once (it has been supplanted, either by a mergen-server runtime restart or by the loser of a spawn race); one that finds NO valid record naming it across three consecutive checks self-exits as an orphan. This is the router-side equivalent of MERGEN_WORKER_IDLE_REAP_HOURS: it exists because a router is spawned detached + unref'd and otherwise has no self-termination condition at all, so a leaked one (a test whose temp DATA_DIR was deleted, a race that overwrote router.json) ran forever until a human ran doctor --fix. Found live 2026-09-10: two 1.6.0 routers on random high ports, days old. The process only ever signals ITSELF; no cross-process kill authority is added. Set to 0 to disable the self-guard.

MERGEN_ROUTER_MAX_LIFETIME_SECONDS

type: int · default: 0

Hard cap on how long a single ADR-017 router process runs before it self-reaps (through its own close(), so workers and records are cleaned up). 0 (the default, and the intended production value) means no cap: a real dev-machine router legitimately runs for days and respawns lazily on the next request anyway. This exists for the test suite: the router autostart integration tests spawn a REAL detached router against a private temp MERGEN_DATA_DIR, and a test that times out (or whose cleanup races the router's startup) leaves that router with a fully self-consistent record of its own that MERGEN_ROUTER_ORPHAN_CHECK_SECONDS's machine-relative checks cannot see is abandoned. setup/isolate-data-dir.ts sets a low value for every test run so such a leak self-clears in a couple of minutes instead of lingering until doctor --fix.

MERGEN_HOST

type: string · default: unset

Server host override used by CLI clients to reach the server.

MERGEN_DATA_DIR

type: string · default: ~/.mergen

Data directory for all on-disk stores: license, the enrolled runtime credential, the durable action ledger, HITL hold state, approved-script trust, override corpus. Set this to a path on a volume that survives a rebuild in Docker, Dev Containers, Codespaces, or Gitpod; the default (home directory) is normally INSIDE the container and is wiped on rebuild, which also re-enrolls the workspace as a new Fleet identity and (see onboarding-window.ts) skips the first-install grace period rather than risk silently disabling an existing team's HOLD approvals.

MERGEN_BUFFER_SIZE

type: int · default: unset

Ring buffer capacity (events).

MERGEN_OTLP_BODY_LIMIT

type: string · default: 16mb

Max request body on /v1/* OTLP routes.

MERGEN_OTLP_TOKEN

type: secret · default: unset

When set, POST /v1/traces and /v1/logs require it as "Authorization: Bearer " (set OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20 on the exporter). Unset, any process that can reach the port may post telemetry; set it when MERGEN_BIND exposes the server beyond this machine. Browser requests from non-loopback origins outside MERGEN_ALLOWED_ORIGINS are refused either way.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_TLS_CERT

type: string · default: unset

TLS certificate path (cloud/team mode).

MERGEN_TLS_KEY

type: string · default: unset

TLS private key path (cloud/team mode).

MERGEN_ALLOWED_ORIGINS

type: csv · default: unset

CORS allow-list in team/cloud mode.

MERGEN_PUBLIC_URL

type: url · default: unset

Externally reachable base URL: HITL approve/deny links, Slack OAuth redirect, OIDC issuer. Required in team/cloud mode.

MERGEN_TRUSTED_PROXY

type: string · default: unset

Express trust-proxy setting for deployments behind a reverse proxy.

MERGEN_CLOUD_MODE

type: boolean · default: false

Multi-tenant SaaS deployment mode: Postgres+Redis required, API-key auth, tenant isolation.

MERGEN_PG_URL

type: secret · default: unset

Postgres connection string (cloud mode). May embed credentials.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_REDIS_URL

type: secret · default: unset

Redis connection string (ring-buffer persistence / cloud mode). May embed credentials.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_BULLMQ

type: boolean · default: unset

Use BullMQ (Redis) for background job queues.

MERGEN_WASM_PATH

type: string · default: unset

Path override for bundled WASM artifacts.

MERGEN_MODEL

type: string · default: unset

Model id for the optional LLM spokesperson (advisory summaries only, never in the enforcement path).

MERGEN_POLICY_DRAFT_AI

type: boolean · default: true

Policies → Add a rule → "Draft a rule from …" (type what to stop in the search box), paid plans: send the sentence you type (and nothing else from this machine) to MERGEN_API_URL, where Claude drafts the rule. You review the draft before it is saved, and a saved rule is enforced by the local deterministic engine like any other. false keeps drafting on this machine (the pattern matcher, which is also the fallback whenever the API is unreachable). Drafting is not available on Free.

MERGEN_API_URL

type: url · default: unset

Activation/licensing backend base URL. On a paid plan this is also the destination for periodic background sync: machine identity/heartbeat, usage counts, resolved approval decisions (including the held command argument), and Agent Blunder Log entries (including blocked command text and reason). None of this is required for local enforcement, and none of it is sent on Free.

MERGEN_SKIP_SIGNIN

type: boolean · default: false

2026-09-05: every plan now requires an account, Free included (no payment for Free); mergen-server setup blocks on the device-auth sign-in flow by default. This opts a scripted/CI install out of that one-time interactive step (an alternative to MERGEN_TOKEN when the install genuinely has no account and no machine token). Never affects Gate A's PASS/BLOCK/HOLD verdict, which stays plan-agnostic and unauthenticated-safe either way; org/cloud-policy features simply keep refusing to work until a real mergen login runs.

MERGEN_BASE_URL

type: url · default: unset

Base URL override for self-referential links.

MERGEN_DASHBOARD_URL

type: url · default: unset

Dashboard base URL override used in notification links.

MERGEN_TELEMETRY

type: boolean · default: unset

Product telemetry opt-in/out.

MERGEN_TELEMETRY_URL

type: url · default: unset

Product telemetry endpoint override.

MERGEN_LS_VARIANT_MAP

type: string · default: unset

LemonSqueezy variant→plan mapping override (billing metadata only; billing never enforces).

MERGEN_MEMBER_ID

type: string · default: unset

Pin this install's identity in usage reports.

MERGEN_USAGE_MODE

type: enum · values: cli · ide · default: unset

Retired 2026-10-07: accepted and ignored. It used to be chosen once at mergen setup and decided which agents setup connected and which surfaces were reported. Now setup protects every agent it finds (--ide/--no-ide narrow that, --terminal adds the shell gate), and the CLI, the IDE panel and the dashboard each show their own controls. A recorded value only tells bare mergen that this install finished setup earlier; the next mergen setup clears it.

MERGEN_SECRET

type: secret · default: unset

Shared secret for the /ingest endpoint (x-mergen-secret). Other admin routes are guarded by ~/.mergen/secret or MERGEN_ADMIN_SECRET.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_ADMIN_SECRET

type: secret · default: unset

Dedicated env-only secret for the admin-mutation guard and sensitive-GET guard. Recommended for production so agents with filesystem access cannot forge admin actions.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_AUDIT_SECRET

type: secret · default: unset

HMAC key for audit evidence-pack signing.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_POLICY_SIGNING_SECRET

type: secret · default: unset

HMAC key for local enterprise-policy.json tamper evidence. Falls back to ~/.mergen/secret when unset (weaker: on-disk).

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_POLICY_HMAC_SECRET

type: secret · default: unset

Legacy HMAC key verifying REMOTE policy-sync responses (X-Mergen-Policy-Signature). Every member holds it, so every member can also sign: prefer MERGEN_POLICY_PUBLIC_KEY. Distinct from MERGEN_POLICY_SIGNING_SECRET (local file signing).

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_POLICY_SIGNING_KEY

type: secret · default: unset

Sync Plane host only: the Ed25519 private key (base64 PKCS#8 DER) the plane signs the served policy with (X-Mergen-Policy-Signature-Ed25519). Made by mergen-server connect sync-plane --plane; never on a member machine.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_POLICY_PUBLIC_KEY

type: csv · default: unset

Sync Plane members: the plane's Ed25519 public key(s), base64 SPKI DER, comma-separated during a rotation. When set, a pulled policy must carry a valid Ed25519 signature and an HMAC-only one is refused.

MERGEN_POLICY_SYNC_TOKEN

type: secret · default: unset

Bearer token for the self-hosted policy sync plane (/policies/sync).

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_AGENT_TOKEN

type: secret · default: unset

Verified agent identity token presented by a registered agent process.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_TOKEN

type: secret · default: unset

CI/CD and ephemeral non-interactive environment machine token.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_PLAN_ID

type: string · default: unset

CI/CD license plan override (used with MERGEN_TOKEN).

MERGEN_AGENT_TOKEN_SECRET

type: secret · default: unset

HMAC key for signing/verifying agent identity tokens.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_SSO_TOKEN

type: secret · default: unset

Bearer token required on mutating requests when MERGEN_SSO_REQUIRED=true.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_USER_ID

type: string · default: unset

FALLBACK human principal on ledger rows. A verified agent token's SSO-federated userId always wins over this unauthenticated value.

MERGEN_ENVIRONMENT

type: string · default: unset

Machine label (production/staging/dev) for a dedicated server, matched by a rule's environments condition. Not needed on a laptop: rules limited to production read what each command targets (targetEnvironments), and with no label an environments rule does too.

MERGEN_REPO

type: string · default: unset

Repository label (org/name) for repos conditions. Optional: by default each call's repository is read from its own git checkout (origin remote); this overrides it.

MERGEN_GIT_BRANCH

type: string · default: unset

Branch for branches conditions. Optional: by default each call's branch is read from its own git checkout (HEAD); this overrides it.

MERGEN_AGENT_ID

type: string · default: unset

Registered agent id for this process, matched by policy agentIds conditions.

MERGEN_CLIENT

type: string · default: unset

Client surface label (claude-code/cursor/cli-hook/…) recorded on gate decisions.

MERGEN_TRACE_ID

type: string · default: unset

Trace id propagated onto ledger rows for cross-tool correlation.

MERGEN_SERVICES

type: csv · default: unset

Known service names for attribution.

MERGEN_TENANT_ID

type: string · default: unset

Tenant id for cloud-mode client calls.

MERGEN_DEFAULT_TENANT_ID

type: string · default: unset

Default tenant id assigned to unattributed events (cloud mode).

MERGEN_SYSTEM_TENANT_ID

type: string · default: unset

Reserved tenant id for system-generated events (cloud mode).

MERGEN_WATCH_INTERVAL_MS

type: int · default: unset · module: process-watch

Process watcher poll interval.

MERGEN_POLICY_URL

type: url · default: unset · module: policy-sync

Central policy server URL for team policy sync. HTTPS enforced at startup.

MERGEN_CLOUD_POLICY_SYNC

type: boolean · default: true · module: cloud-policy-sync

Automatically pull and activate the My Organization policy on an interval, for signed-in paid-plan installs. false = cloud policy sync only via the manual policy pull --cloud command.

MERGEN_CLOUD_POLICY_POLL_MS

type: int · default: 120000 · module: cloud-policy-sync

Interval between automatic My Organization policy sync attempts, in ms.

MERGEN_AUTH_STUB

type: boolean · default: false

DEV ONLY: serve a local device-authorization stub standing in for api.mergen.app so activation runs offline. Affects licensing/plan metadata only, never enforcement.

MERGEN_AUTH_STUB_PLAN

type: string · default: unset

DEV ONLY: plan the auth stub grants.

MERGEN_AUTH_STUB_EMAIL

type: string · default: unset

DEV ONLY: account email the auth stub reports.

MERGEN_AUTH_STUB_GITHUB_LOGIN

type: string · default: unset

DEV ONLY: fake GitHub login the offline Connect-GitHub broker stub reports.

MERGEN_AUTH_STUB_SLACK_TEAM

type: string · default: unset

DEV ONLY: fake Slack workspace name the offline Connect-Slack broker stub reports.

Integrations

Connections to external systems (Slack, PagerDuty, SIEM, email, …). Integrations deliver notifications and reports; the security decision itself never depends on one being configured.

MERGEN_OKTA_DOMAIN

type: string · default: unset · module: idp

Okta domain for IDP identity enrichment.

MERGEN_ENTRA_TENANT_ID

type: string · default: unset · module: idp

Microsoft Entra tenant for IDP identity enrichment / Azure federation.

MERGEN_OIDC_CLIENT_ID

type: string · default: unset · module: idp

OIDC client id for IDP identity enrichment.

MERGEN_OIDC_ISSUER_URL

type: url · default: unset · module: idp

External OIDC issuer URL for verifying operator identities.

MERGEN_OIDC_JWKS_URL

type: url · default: unset · module: idp

JWKS endpoint override for the external OIDC issuer.

MERGEN_SLACK_BOT_TOKEN

type: secret · default: unset · module: slack

Slack Web API token (threads, replies, HITL delivery fallback). Optional once the OAuth install flow has persisted a token.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_SLACK_CHANNEL

type: string · default: unset · module: slack

Default incident channel.

MERGEN_SLACK_SIGNING_SECRET

type: secret · default: unset · module: slack

HMAC secret verifying inbound Slack interactions; without it every interactive button click is rejected with 401.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_SLACK_WEBHOOK

type: secret · default: unset · module: slack

Simple-mode incoming webhook for alerts with link buttons.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_SLACK_CLIENT_ID

type: string · default: unset · module: slack

Slack app OAuth client id: enables /slack/install.

MERGEN_SLACK_CLIENT_SECRET

type: secret · default: unset · module: slack

Slack app OAuth client secret for the install flow.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_SLACK_DIGEST

type: boolean · default: unset · module: slack

Post the daily operational digest at 09:00 UTC.

MERGEN_SLACK_DIGEST_CHANNEL

type: string · default: unset · module: slack

Channel override for the daily digest.

MERGEN_TEAM_DIGEST

type: boolean · default: unset · module: slack

Post the weekly team digest Mondays 09:15 UTC (reports only, never HITL).

MERGEN_HITL_WEBHOOK_URL

type: secret · default: unset · module: hitl

Explicit HITL delivery webhook (first in preference order). The HOLD itself always runs on every plan; delivery is the convenience.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_HITL_SECONDARY_WEBHOOK

type: secret · default: unset · module: hitl

Delegation chain: fired at the 5-min mark if the primary channel produced no response.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_HOLD_NOTIFY

type: enum · values: off · on · default: off · module: hitl

Desktop notification (macOS osascript / Linux notify-send / Windows PowerShell toast) the moment a HOLD is placed, for a terminal-first user with no Slack/webhook and no mergen monitor open. Off by default (2026-09-29: the toast arrives as "Script Editor" on macOS, which read as noise, not as Mergen); opt in with mergen notify holds-only; fire-and-forget, never blocks or affects the hold. The HOLD and its 15-min auto-deny are unchanged either way. Nothing is spawned where a toast cannot land (headless Linux, missing notifier) - mergen notify reports that case rather than claiming the event is on. Subsumed by MERGEN_NOTIFY below (still honored: on = MERGEN_NOTIFY=hold).

MERGEN_NOTIFY

type: string · default: `` · module: hitl

Comma list of gate events to fire a native desktop toast on: block, hold, hold-expiry (or all). The CLI-side parity for the VS Code extension's notifyOnBlock/notifyOnHold/notifyOnHoldExpiry. Default off; each fire is fire-and-forget and never affects the verdict, delivery, or the 15-min auto-deny. macOS osascript (terminal-notifier when installed) / Linux notify-send / Windows PowerShell toast; other platforms skip silently. Events of one kind arriving in a burst coalesce into a single toast.

MERGEN_NOTIFY_COMMAND

type: string · default: `` · module: hitl

Shell command to pipe each HOLD event JSON to (via stdin). Fires on every HOLD regardless of whether an IDE extension is watching; designed for headless/SSH environments, custom ntfy/email delivery, or any channel the built-in notifiers don't cover. Example: ntfy publish my-topic. The JSON payload matches the hold-notice file: { timestamp, kind, toolName, commandArg, reason, body, action }. Fire-and-forget; never affects the hold lifecycle.

MERGEN_PAGERDUTY_SECRET

type: secret · default: unset · module: pagerduty

INBOUND webhook HMAC signing secret. Required in cloud mode and under autopilot.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_PAGERDUTY_ROUTING_KEY

type: secret · default: unset · module: pagerduty

OUTBOUND Events API v2 key: pages when HITL approvals go unanswered for 10 min. Distinct from the inbound signing secret.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_PAGERDUTY_OAUTH_CLIENT_ID

type: string · default: unset · module: pagerduty

PagerDuty OAuth App client id (BYO app): enables /pagerduty/install. Falls back to the hosted Connect broker when unset. Distinct from MERGEN_PAGERDUTY_ROUTING_KEY, which this OAuth flow provisions automatically.

MERGEN_PAGERDUTY_OAUTH_CLIENT_SECRET

type: secret · default: unset · module: pagerduty

PagerDuty OAuth App client secret for the BYO install flow.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_PR_COMMENTS

type: boolean · default: unset · module: github

Post AI code review comments on PRs (enables habituation tracking).

MERGEN_GITHUB_CLIENT_ID

type: string · default: unset · module: github

GitHub App/OAuth App client id: enables /github/install.

MERGEN_GITHUB_CLIENT_SECRET

type: secret · default: unset · module: github

GitHub App/OAuth App client secret for the install flow.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_SIEM_WEBHOOK_URL

type: url · default: unset · module: siem

Generic SIEM webhook: blunders + forwarded ledger rows POSTed as JSON. Any SIEM that ingests an HTTP JSON webhook (directly or via a collector) consumes this. Mergen ships one generic export, not per-vendor connectors.

MERGEN_SIEM_WEBHOOK_TOKEN

type: secret · default: unset · module: siem

Bearer token for the generic SIEM webhook.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_EMAIL_DIGEST

type: boolean · default: unset · module: email

Enable the daily digest email.

MERGEN_SMTP_URL

type: secret · default: unset · module: email

Single-URL SMTP config (may embed credentials).

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_SMTP_HOST

type: string · default: unset · module: email

SMTP host (discrete form).

MERGEN_SMTP_PORT

type: int · default: 587 · module: email

SMTP port.

MERGEN_SMTP_USER

type: string · default: unset · module: email

SMTP username.

MERGEN_SMTP_PASS

type: secret · default: unset · module: email

SMTP password.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_SMTP_SECURE

type: boolean · default: unset · module: email

Explicit TLS override (default true only on port 465).

MERGEN_EMAIL_FROM

type: string · default: unset · module: email

Digest sender address.

MERGEN_EMAIL_TO

type: csv · default: unset · module: email

Digest recipients.

MERGEN_NTFY_TOPIC

type: string · default: unset · module: notifications

ntfy.sh topic name.

MERGEN_NTFY_URL

type: url · default: https://ntfy.sh · module: notifications

ntfy server.

MERGEN_NTFY_TOKEN

type: secret · default: unset · module: notifications

ntfy access token.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

MERGEN_FLEET_SYNC_URL

type: url · default: unset · module: fleet-audit

Self-hosted sync plane URL member installs report audit rows to (single-team, own-infra).

MERGEN_LEDGER_SYNC_URL

type: url · default: unset · module: cloud-ledger-sync

ADR-013: base URL of a MERGEN_CLOUD_MODE=true Mergen deployment this install pushes its local action-ledger/blunder-log rollup to, for cross-install visibility under that deployment's own tenant. Distinct from MERGEN_API_URL (Mergen's own hosted licensing/Governance Console) and MERGEN_FLEET_SYNC_URL (the self-hosted sync plane): this targets a customer-operated cloud-mode Mergen server's own POST /v1/ledger-batch. Requires MERGEN_LEDGER_SYNC_API_KEY. Silent/no-op when either is unset; Gate A's own local write path never depends on this.

MERGEN_LEDGER_SYNC_API_KEY

type: secret · default: unset · module: cloud-ledger-sync

Tenant API key (x-api-key) for MERGEN_LEDGER_SYNC_URL's cloudAuthMiddleware, issued by that deployment via POST /api-keys, identifies which tenant this install's pushed rows are stamped under.

Secret: set via an env-only mechanism (secrets manager, systemd EnvironmentFile). The value is never rendered by any Mergen surface and is rejected from the signed config file.

Experimental flags

MERGEN_EXPERIMENTAL_* flags are undocumented, unstable, and bound by monotonic strictness: they may only add capability or escalate PASS→HOLD, never weaken a verdict.

Modules & tiers

The capability manifest (server/src/config/modules.ts): inspect live state on any install with mergen-server config modules or GET /config/modules.

Trusted Core: no off switch exists in code

  • Execution Gateway: The Claude Code/Windsurf Bash+Edit PreToolUse hook, Cursor/VS Code self-heal, and mergen-server exec. Every covered call passes the gate before its handler runs.
  • Policy Engine: Deterministic <1ms rule evaluation → PASS / HOLD / BLOCK. Immutable hard-safety rules cannot be edited or bypassed by any setting.
  • Verdicts & Guided Alternatives: Every BLOCK/HOLD carries why it happened and what to do instead, so agents reformulate instead of stopping.
  • Audit Recording: Action ledger (every PASS/HOLD/BLOCK), hash-chained Agent Blunder Log, usage meter. Recording is core; forwarding is an integration; retention is config.
  • Human-in-the-Loop Holds: HOLD suspends the call for human decision and fails closed (15-min auto-deny). The hold is core on every plan; delivery channels are integrations.
  • Admin Auth Guards: Mutation + sensitive-GET guards over every admin surface, including the signed org-policy settings file.
  • PII Shield: Always-on redaction patterns (emails, keys, JWTs, cards) on stored/streamed content; extendable, never disableable.

Modules: toggleable; disabling can only make the gate same-or-stricter

  • Incident Autopilot (Gate B): Autonomous diagnose→fix→validate loop on incidents. Off by default; shadow mode is its staging tier.
  • Override Corpus: Human overrides encoded as enforcement policy; feeds Gate B always and Gate A via the four explicit opt-in paths.
  • Policy Suggester: Stages repeatedly-overridden patterns as HOLD-only proposals for one-click human approval. Never auto-activates, never proposes a BLOCK.
  • Edit-Time Change Gate: Claude Code, GitHub Copilot CLI, and Windsurf pre-write hooks plus Cursor/VS Code self-heal integrations: deterministic diff-content detectors (secret introduction, test deletion, auth-bypass, CI-check removal, permission/config weakening) run at edit time, before the write lands. Governs what code an agent is allowed to leave behind, distinct from Gate A (tool-call execution) and Gate B (autopilot fixes). Each detector acts at its own shipped rung (MERGEN_EDIT_GATE_DETECTOR_MODES); MERGEN_EDIT_GATE_MODE=off is the kill switch.
  • Script Trust: Opaque-script escalation: untracked/fresh scripts hold for approval; approval vouches for exact content (HMAC-signed store). Window=0 disables only the freshness check; git-tracking and workspace checks stay on.
  • Exec Sandbox: Runs mergen-server exec commands in Docker or macOS Seatbelt isolation instead of directly on the host.
  • Agent & Human Identity: Verified agent tokens with SSO-federated human principals bound in; RBAC roles for approvals.
  • Incident Memory & Diagnosis: Causal analysis, incident similarity, postmortem retrieval: the context brief that cuts MTTR.
  • Process Watchers: Local process/port auto-watch feeding runtime telemetry into the buffer.
  • Degradation Watcher: Background watcher that flags sustained error-rate degradation.
  • Confidence Calibration: Platt-scaled confidence from engineer verdict tags; governs Gate B autonomy thresholds only.

Integrations: delivery & ingest channels

  • Slack: Incident threads, HITL approve/deny buttons, daily + weekly digests.
  • HITL Delivery Webhooks: Explicit webhook chain for hold notifications (primary + 5-min secondary escalation).
  • PagerDuty: Inbound incident webhooks (signed) + outbound paging when a high-risk HITL approval goes unanswered (rules with riskTier: high page at 10 minutes, or at 3 when no other channel took the hold; escalate: true rules page at 3). Routine approvals never page.
  • GitHub: Webhook-fed commit context for incident diagnosis, plus informational PR comments. Never gates a PR or a merge.
  • Datadog: Trace fetch + blame attribution during triage.
  • SIEM Forwarding: Blunders + selected ledger verdicts streamed to a generic JSON webhook any SIEM can ingest.
  • Email Digest: Daily/weekly digest sink. Deliberately NEVER a HITL channel (pinned by test).
  • Push Notifications: ntfy.sh alert channel.
  • IDP Enrichment (Okta / Entra): Operator identity enrichment from the org IDP.
  • Policy Sync Plane: Pulls the team's shared policy from a central instance (HTTPS + HMAC verified).
  • Cloud Policy Sync: Automatically pulls and activates the My Organization policy for signed-in paid-plan installs (distinct from the self-hosted Policy Sync Plane above).
  • Fleet Audit Sync: Member installs report ledger rollups to a self-hosted sync plane (single-team, own infra).
  • Cloud Ledger Sync: ADR-013: pushes this install's local action-ledger/blunder-log rollup to a customer-operated MERGEN_CLOUD_MODE=true Mergen deployment's authenticated ingestion endpoint (POST /v1/ledger-batch), tenant-stamped there from the API key; never by Gate A, which stays tenant-agnostic and never writes to Postgres directly.
  • Redis Persistence: Ring-buffer persistence across restarts.

Last updated October 31, 2026