Skip to main content
Version: v5.7.0

2.3 Waits and gates — the policy way

Included from Basic tier

After this you can: pause an action with a wait step, and know that approval gates are imposed by policy on the path a change runs — not written into the action.

Before you start — tick each one:

  • Lesson 2.2 — Namespaces, composition and inputs (opschain learn 2.2)

In the GUI: a held run sits on Inbox → Approvals with its approval context, and the sidebar's Inbox wears a red bubble with the count waiting on you.

Some steps need a human: a maintenance window to pick, a sign-off before production moves. This lesson separates the two cleanly. A wait step is authoring — it belongs in your action file. An approval is governance — it belongs in a policy on the server, and nothing in the action file names an approver.

1. Wait steps​

A wait: step pauses the run for a human. Locally it prompts right in your terminal; declared inputs are prompted one by one with their defaults:

action: pick
steps:
- name: pick_window
wait:
inputs:
- name: maintenance_window
default: "02:00-04:00"
description: Deployment window
- name: after
run: echo proceeding
opschain runs create change pick --env dev --plan --local
· pick
├─ · pick_window
└─ · after

Run it from a terminal and the prompt appears at pick_window; answer it and after runs. Run it where no terminal can answer — a script, CI — and the run cancels loudly rather than hanging:

2026-11-07T00:26:25Z pick/pick_window pending -> waiting
2026-11-07T00:26:25Z pick/pick_window waiting -> cancelled
2026-11-07T00:26:25Z pick/after pending -> aborted
2026-11-07T00:26:25Z pick running -> cancelled
Error: prompter failed for step pick/pick_window: step pick/pick_window is waiting for input but stdin is not an interactive terminal — re-run with --approve-all, or run from a terminal

The exit code is 3 — cancelled, the same code a run you interrupt exits with — and the refusal names the switch. --approve-all answers every wait step with its defaults — the switch for unattended runs:

opschain runs create change pick --env dev --local --approve-all
2026-11-07T00:25:50Z pick/after queued -> running
[pick/after] proceeding
2026-11-07T00:25:50Z pick/after running -> success
2026-11-07T00:25:50Z pick running -> success

run 01a1b365-b6aa-73f0-94ad-f9ce78b156bc success (0.1s)

On a server the same step waits in the record, and whoever may act on the node answers it with opschain runs approve <run-id> --step <step-id> (lesson 3.2). That is a wait, not an approval: it asks a person for input, it does not say which person must sign off.

2. Approval gates live in policies​

To require a governed sign-off — named approvers, quorum, an audited decision — a change manager writes an approval policy on the server. The policy names a path pattern; every change created under that path is held for approval before it starts, whatever action file it runs:

opschain approval-policies create --name 'Production change control' \
--path-pattern '/projects/acme/environments/prod/**' \
--approver-group release_managers --quorum 1

From then on a change created under prod is created normally — its step tree freezes and a fingerprint is taken — and then held: pending_approval in every listing, a checklist opened in the approvers' inbox, nothing driven, no runner, no queue slot. The approvers answer with opschain approvals approve|reject <run-id> and read the inbox with opschain approvals list; the decision history is opschain approvals history.

The point for you as an author: the action file does not change. Who signs off is governance, not authoring — the same deploy action runs ungated in dev and held in prod, decided by the path it runs on. Writing policies needs the packs:change-governance entitlement and the policy-writer role; opschain learn governance-approvals walks the whole funnel from the change manager's seat.

3. A note on the legacy key​

Repositories written before approval policies may carry requires_approval_from: (user_names:/ldap_groups:) on a wait step — an in-YAML approval gate only the named users or directory groups may answer mid-run. It still works and is still enforced, but it is the superseded form: do not write it in new actions. For stored workflows, opschain approval-policies convert-step-gates inventories every such gate and creates the equivalent policies without touching the YAML; for repository actions, create the policy at the owning path and delete the key at your own pace.

Check yourself​

  • Where does an approval requirement live for a production change, and what does the legacy in-YAML key do today?

What you learned​

  • A wait: step pauses a run for human input; locally it prompts in your terminal, and --approve-all answers it with the defaults.
  • A wait asks a person for input; an approval names who must sign off — and that lives in an approval policy on the server, matched by path.
  • A change under a policy's path is created, then held pending_approval until the approvers decide, with the action file unchanged.
  • requires_approval_from: is the superseded in-YAML form; convert-step-gates turns stored workflows' gates into policies.

Next: lesson 2.4 — Using packs (opschain learn 2.4), or back to your track (opschain learn).