Skip to main content

Approval gates

An approval gate pauses a target until you explicitly approve or reject it. Use it for targets that have irreversible effects — deploys, infrastructure changes, database migrations — where an accidental run must be prevented.

Declaring an approval gate​

Add approval = True to any target:

BUILD
target(
name = "deploy",
driver = "bash",
approval = True,
run = ["./deploy.sh"],
)

The map form lets you attach a notice — content shown to you before you decide:

BUILD
target(
name = "deploy",
driver = "bash",
approval = {"required": True, "notice": ["plan"]},
deps = {"plan": ["//infra:plan"]},
run = ["./apply.sh"],
)

notice is a list of input group names. Before asking for your decision, heph resolves each named group and displays the contents of its files. In the example above the output of //infra:plan is shown so you can review what will change before approving.

approval field shape​

ValueMeaning
TrueGate is required; no notice.
False (default)No gate.
{"required": True, "notice": ["group", ...]}Gate is required; listed input groups are rendered as the notice.
{"required": False, "notice": [...]}Notice only — content is shown but no approval is required.

Unknown keys in the map form are an error, so typos fail loudly instead of silently disabling the gate.

Interactive prompt​

When you run a gated target in a terminal, heph pauses and waits for your decision:

  • Press y to approve and continue execution.
  • Press n to reject — the target fails immediately with a clear error.

In the full TUI the prompt appears inline on the progress view. In a plain terminal session the notice prints to stderr and heph reads your answer from the controlling terminal.

Non-interactive runs​

With no terminal attached, a gated target fails unless you pass --auto-approve:

terminal
heph run //infra:deploy --auto-approve

--auto-approve approves all gated targets in the run without prompting. The notice still prints to stderr — the decision is automatic but the notice is never suppressed.

warning

Running without a terminal and without --auto-approve fails with a clear error asking you to pass the flag. This is intentional — a gated target must never execute silently.

Cache interaction​

A cache hit skips execution entirely — and with it, the approval gate. For targets that must always prompt (a deploy, for example), disable caching:

BUILD
target(
name = "deploy",
driver = "bash",
approval = True,
cache = False,
run = ["./deploy.sh"],
)

With cache = False the target always runs, so the gate always fires.