← All docs

Fail-Open vs. Fail-Closed: the local execution gate's reliability posture

Mergen sits inline in front of the agent's shell commands and file edits. That makes reliability a first-class safety property: a gate that misbehaves must not silently stop enforcing (fail-open) and must not permanently brick a developer's terminal (fail-closed with no escape). This document states, precisely, what the gate does in every failure mode, and how an operator recovers from a broken gate.

TL;DR, in two halves that are routinely confused:

  1. Internal faults fail CLOSED. A rule that throws, a corrupt policy file, a command that will not parse. Each resolves to BLOCK or HOLD, never a silent PASS. This has always been true and still is.
  2. Only a gate that is not running at all can let a call through, and only on a machine no organization manages. There MERGEN_GATE_AVAILABILITY_MODE defaults to fail-open: when nothing answers, there is no verdict to enforce and the tool call proceeds unevaluated. Set fail-closed to block instead. A machine an organization manages always fails closed, whatever is set locally. A gate that is running but too slow (a timeout) or answers with an error blocks in every mode.

There are therefore two paths on which a call can run without a real enforcement decision: the fail-open default above, on an unmanaged machine, and the bounded, operator-only, fully-audited panic bypass.


Principle

A fault must never become a SILENT pass. When the gate reaches a verdict but something inside it went wrong (evaluation errored, the policy file is corrupt, a command will not parse), it fails closed (BLOCK, or HOLD for human review). No probabilistic guardrails, no silent enforcement downgrade, even when Mergen itself is the thing that broke.

Availability is a separate question, and a configured one. If the gate cannot be reached at all, there is no verdict to fail closed from. MERGEN_GATE_AVAILABILITY_MODE decides what happens, and since 2026-09-19 it defaults to fail-open: a stopped runtime, one nothing answers for, lets the call through. Three things narrow that:

  • An organization-managed machine fails closed. A machine signed in under an organization, or one that has applied an organization's policy, blocks when the gate cannot answer. A local fail-open cannot loosen it.
  • A timeout is not an absent gate. A gate that is running but does not answer in time blocks, in every mode: it may be holding the call for a person.
  • An error is not an absent gate. A gate that answers with an error (401, 413, 429, 500) blocks, in every mode, and says how to recover. Only "nothing is running" (connection refused, or 502/503/504 from a router whose worker is gone) takes the fail-open path.

That default is deliberate (a background daemon being down should not brick a developer's every Bash call), and it is not silent. mergen doctor --security, GET /coverage/reliability, and the startup banner all report it, and a stock install reports overallPosture: advisory rather than hardened partly because of it.

The word doing the work in the principle is silent. A documented, reported, one-setting-away allow path is a tradeoff a team can weigh. An undocumented one is a lie.


Behavior in every failure mode

Failure Behavior
Policy evaluation throws (malformed rule, unexpected input) Fail-closed → BLOCK, logged, pipeline_block audit entry
Gate heartbeat stale/dead (MERGEN_REQUIRE_GATE_HEARTBEAT) Fail-closed → BLOCK
enterprise-policy.json fails HMAC / schema validation Fall back to the built-in default policy (still enforcing), policy_integrity_failure audit entry
Script/command can't be structurally parsed Fail-closed → HOLD (obfuscation-as-signal, script-trust)
No local gate reachable at all (hook/CLI call, nothing running) Organization-managed machine: fail-closed → BLOCK. Otherwise MERGEN_GATE_AVAILABILITY_MODE governs: fail-open by default (the call runs unevaluated); fail-closed blocks. Auto-start is attempted first on a clean unreachable
Gate running but does not answer in time Fail-closed → BLOCK, every mode (it may be holding the call for a person)
Gate answers with an error (401, 413, 429, 500) Fail-closed → BLOCK, every mode, with the way out
Operator activates the panic bypass Fail-OPEN → PASS, every call panic_bypass-audited

Two rows above can let a call through without an enforcement decision: the unreachable-gate row under its fail-open default, and the panic bypass. Every other row fails closed.

The two-command bypass, stated plainly

Under the default, an agent with shell access can run:

mergen-server stop && <anything>

and the second command executes unevaluated. Setting MERGEN_GATE_AVAILABILITY_MODE=fail-closed closes it.

This is named here rather than left for a reader to derive, because a security control's known bypass belongs in its own documentation. mergen-server stop does leave a suppression marker, and gate-check keys on its presence rather than on who wrote it (stoppedBy is derived from isTTY, which an agent can spoof with a pseudo-TTY), but under fail-open that marker changes what gets reported, not the verdict.

Choosing a mode

fail-closed: the stronger posture, and the recommended one for shared machines, CI, and any deployment where the agent is less trusted than the developer running it. An agent with write access to prod is the threat model: if the gate stopped enforcing whenever it became unreachable, an attacker (or a confused agent) would be incentivised to induce that state. Fail-closed removes the incentive: breaking the gate stops work, it does not unlock it.

fail-open (default on a machine no organization manages): favours availability on a developer's own machine. The judgement is that on a single-developer workstation, a Mergen process that died at 2am should not block every command until someone notices, and that the realistic threat there is a mistaken agent rather than one deliberately engineering a bypass. Auto-start (MERGEN_GATE_AUTOSTART) is what makes this recoverable in practice: a clean unreachable respawns the server and evaluates against the fresh gate rather than simply allowing.

The cost of fail-closed, and the fix

Fail-closed has a real failure mode of its own: a bad policy edit, or a gate bug, can BLOCK every command and halt the developer until Mergen is fixed or restarted. That is exactly what the panic bypass exists to release, without abandoning the audit trail.


The panic bypass

An operator-only, time-bounded, fully-audited liveness escape hatch. While active, the gate passes every tool call without enforcement, but records each one as a loud, hash-chained panic_bypass entry in the Agent Blunder Log noting what the policy would have done.

mergen-server panic on --minutes 15 --reason "bad policy edit blocking all commands"
mergen-server panic status
mergen-server panic off

Ephemeral/containerised alternative (no persisted file):

MERGEN_PANIC_BYPASS_UNTIL=$(( $(date +%s%3N) + 900000 ))   # epoch-ms, 15 min out
MERGEN_PANIC_BYPASS_REASON="incident-1234"

Guarantees

  • Bounded. Every window has an expiry; the TTL is clamped to a hard maximum (4h) so it can never be left on indefinitely by mistake. An expired window reads as inactive and its stale file is cleaned up.
  • Loud. Activation logs at error level. Every bypassed call is a hash-chained panic_bypass audit entry, visible in GET /agent-blunders, the SIEM stream, and evidence-pack exports, so any action taken while enforcement was off is fully reviewable after the fact.
  • Checked first. The bypass is evaluated before the heartbeat fail-closed and every policy check, so it can un-brick a gate whose evaluation itself is the fault (the "would have" verdict is best-effort and wrapped: a broken evaluator can't re-brick the call it's meant to release).
  • Fails closed on ambiguity. A missing or corrupt panic file reads as inactive (enforce), never active. Absence of a proven bypass means enforce.

Threat-model honesty

The panic bypass is not a security boundary against a same-user process: an agent with the developer's shell can already write the panic file or set the env var, exactly as it could kill the Mergen process. That is acceptable because the bypass hides nothing: its entire contract is "trade enforcement for liveness, temporarily, in exchange for a complete audit trail." Abuse is loud, not silent. A hard security boundary against a hostile same-user process requires OS-level privilege separation, which is out of scope for the Layer-1 local gate (see the Inside-Out progression in the project README).


Operator runbook: "the gate is blocking everything"

  1. Confirm it's the gate: mergen-server panic status (is it already on?), check recent GET /agent-blunders for a flood of pipeline_block / policy_integrity_failure.
  2. Release the developer: mergen-server panic on --minutes 15 --reason "<what happened>".
  3. Fix the root cause, usually a bad ~/.mergen/enterprise-policy.json edit (mergen-server policy-rollback) or a stale heartbeat (restart Mergen).
  4. Re-enforce: mergen-server panic off.
  5. Review what got through while off: GET /agent-blunders?type=panic_bypass.

Last updated October 31, 2026