Skip to main content

GATE A WORKFLOW WITH MANUAL APPROVAL

This guide shows you how to hold a workflow at a human sign-off before it touches production: add a manual_approval gate, run the workflow until it suspends, review the pending gate out of band, then approve or reject and resume.

Use this when a run reaches a point that needs a person to confirm — a production rollout after staging is verified, a destructive migration, or any step whose blast radius warrants a second pair of eyes.

Prerequisites

  • Swamp installed and on your PATH
  • An initialised repo (swamp repo init)
  • Familiarity with workflow definitions

Add a gate to the workflow

Place a manual_approval step between the work that prepares a change and the work that commits it. Make the downstream step depend on the gate so it cannot run until the gate is approved:

jobs:
  - name: rollout
    steps:
      - name: deploy-staging
        task:
          type: model_method
          modelType: command/shell
          modelName: staging-deploy
          methodName: execute
          inputs:
            run: echo "deployed to staging"
      - name: production-gate
        dependsOn:
          - step: deploy-staging
            condition:
              type: succeeded
        task:
          type: manual_approval
          prompt: "Staging verified. Approve production rollout?"
          timeout: 3600
      - name: deploy-production
        dependsOn:
          - step: production-gate
            condition:
              type: succeeded
        task:
          type: model_method
          modelType: command/shell
          modelName: production-deploy
          methodName: execute
          inputs:
            run: echo "deployed to production"

The prompt is what the approver sees. Set timeout (in seconds) when a stale approval should be refused — see Expire a stale gate.

Run the workflow until it suspends

Run the workflow. It executes up to the gate and then suspends:

swamp workflow run gated-deploy

You will see output like:

 rollout │ waiting approval required: "Staging verified. Approve production rollout?"

 rollout │ To approve:  swamp workflow approve gated-deploy production-gate --run e6551b9e-64fe-47bf-9f8e-eea982cc08b1
 rollout │ To reject:   swamp workflow reject gated-deploy production-gate --run e6551b9e-64fe-47bf-9f8e-eea982cc08b1

  system │ Suspended workflow gated-deploy — awaiting approval on step production-gate · 01:42:41 UTC

  system │ To approve:  swamp workflow approve gated-deploy production-gate --run e6551b9e-64fe-47bf-9f8e-eea982cc08b1
  system │ After approval:  swamp workflow resume gated-deploy --run e6551b9e-64fe-47bf-9f8e-eea982cc08b1

Each hint is a single shell-safe line you can copy as is. With --server, the hints carry the same --server value.

The run now holds: the staging step is succeeded, the gate is waiting_approval, and the production step stays pending.

Verify out of band, then decide

Do whatever the gate exists to confirm — check staging metrics, run smoke tests, get a colleague's eyes on it. The run waits while you do.

To see every gate currently awaiting a decision across all workflows:

swamp workflow approvals
workflow·approvals "gated-deploy" / "production-gate" — "Staging verified. Approve production rollout?"
workflow·approvals   Run:          "e6551b9e-64fe-47bf-9f8e-eea982cc08b1"
workflow·approvals   Suspended at: "2026-10-06T01:42:30.665Z"
  swamp workflow approve gated-deploy production-gate --run e6551b9e-64fe-47bf-9f8e-eea982cc08b1
  swamp workflow reject  gated-deploy production-gate --run e6551b9e-64fe-47bf-9f8e-eea982cc08b1
  After approval: swamp workflow resume gated-deploy --run e6551b9e-64fe-47bf-9f8e-eea982cc08b1

A gate only appears here while it is still within its approval window. Once a gate's timeout lapses it drops out of this listing, so an empty list after the deadline is expected — the gate can no longer be approved.

If the change is good, approve the gate. Pass --reason to record why on the run's audit trail:

swamp workflow approve gated-deploy production-gate --reason "Staging metrics nominal"
workflow·approve Approved step "production-gate" in workflow "gated-deploy"
After approval: swamp workflow resume gated-deploy --run e6551b9e-64fe-47bf-9f8e-eea982cc08b1

If the change is not safe to proceed, reject it. A rejection marks the whole run failed:

swamp workflow reject gated-deploy production-gate --reason "Error budget exhausted"
workflow·reject Rejected step "production-gate" in workflow "gated-deploy"
workflow·reject Workflow run marked as failed.

Resume after approval

Approving records the decision but does not run the remaining steps. Resume the run to execute everything after the gate:

swamp workflow resume gated-deploy

Resume skips the steps that already completed and picks up at the gate's dependents:

  system │ Starting workflow gated-deploy · 01:43:35 UTC

 rollout │ start 01:43:35 UTC
 rollout │ step deploy-production · production-deploy · execute · start 01:43:35 UTC
 rollout │ deployed to production
 rollout │ done deploy-production in 34ms · 01:43:35 UTC
 rollout │ completed in 37ms · 01:43:35 UTC

  system │ Completed workflow gated-deploy succeeded in 3.4s · 01:43:35 UTC

If you resume before approving, the run refuses and names the step to approve first:

Error: Step "production-gate" is still awaiting approval. Run "swamp workflow approve gated-deploy production-gate" first.

Approve a gate inside a nested workflow

When a gate sits in a child workflow that a parent runs with a type: workflow step, the parent suspends too, on that step. The child is a suspended run of its own: decide its gate and resume it under the child's name and run id, then resume the parent.

swamp workflow run parent
 release │ waiting approval required in nested workflow child: "Approve the release?"

 release │ To approve:  swamp workflow approve child release-gate --run cb477441-5a29-45d6-9c1f-6add93aa93bd
 release │ To reject:   swamp workflow reject child release-gate --run cb477441-5a29-45d6-9c1f-6add93aa93bd

  system │ Suspended workflow parent — step run-release waits on nested workflow child run cb477441-5a29-45d6-9c1f-6add93aa93bd · 01:29:20 UTC

  system │ Nested run:  approve its gate as shown above, then  swamp workflow resume child --run cb477441-5a29-45d6-9c1f-6add93aa93bd
  system │ Once it finishes:  swamp workflow resume parent --run 3ca75492-f62a-434c-ae11-f6e3557027a6

Run the three commands in order:

swamp workflow approve child release-gate --run cb477441-5a29-45d6-9c1f-6add93aa93bd
swamp workflow resume child --run cb477441-5a29-45d6-9c1f-6add93aa93bd
swamp workflow resume parent --run 3ca75492-f62a-434c-ae11-f6e3557027a6

The parent's step takes the child's outputs, and the parent runs to completion. swamp workflow approvals lists the child's gate, with the parent resume to run once the child finishes.

If you resume the parent before the child finishes, the resume is refused and names the commands that settle the child:

Error: Run 3ca75492-f62a-434c-ae11-f6e3557027a6 of workflow "parent" waits on a nested workflow run that has not finished. Nested run cb477441-5a29-45d6-9c1f-6add93aa93bd of workflow "child" awaits approval on step "release-gate": 'swamp workflow approve child release-gate --run cb477441-5a29-45d6-9c1f-6add93aa93bd', then 'swamp workflow resume child --run cb477441-5a29-45d6-9c1f-6add93aa93bd'. Then resume this run with 'swamp workflow resume parent --run 3ca75492-f62a-434c-ae11-f6e3557027a6'.

To reject, reject the child's gate, then resume the parent so it reads the outcome. The parent's step fails as a rejected approval:

swamp workflow reject child release-gate --run da7f002a-0285-464f-b79f-08a1207ab3a5 --reason "not today"
swamp workflow resume parent --run 9f4dd894-b96e-4862-9fcb-e4a7778efa0e
system │ Failed workflow parent in 8.6s · 01:29:55 UTC
system │ Approval of step "release-gate" in nested run da7f002a-0285-464f-b79f-08a1207ab3a5 of workflow "child" was rejected.

Under swamp serve, you do not need the parent resume: once the child finishes, serve resumes the parent itself, provided the parent workflow's auto-resume policy is on and the approver holds the approve grant on the parent.

Cancelling or rejecting the parent does not end the child. The child stays suspended, and the output names the command that cancels it:

swamp workflow cancel parent --run 5269d62a-11e7-4b3b-ac1c-b1ad3dde52b3
workflow·cancel Cancelled run "5269d62a-11e7-4b3b-ac1c-b1ad3dde52b3" of workflow "parent"
workflow·cancel Status: "suspended" -> "cancelled"
workflow·cancel Nested run "d2d685ac-d19c-4ee3-ab59-084ec84695d4" of workflow "child" was left unfinished.
Cancel nested run d2d685ac-d19c-4ee3-ab59-084ec84695d4 with: swamp workflow cancel child --run d2d685ac-d19c-4ee3-ab59-084ec84695d4

Run that command to cancel the child.

Approve and resume from the dashboard

When the workflow runs on a swamp serve instance with the dashboard enabled, you can decide and resume the gate in the browser:

  1. Open /dashboard/approvals and click Approve (or Reject) on the gate.
  2. Under Awaiting resume, click Resume on the run.

A dashboard resume supplies no new inputs. If the steps after the gate need values at resume, run the swamp workflow resume command the dashboard shows, with --input. For a credential minted at the gate, see Supply a secret when resuming.

Skip the manual resume

To have serve resume the run as soon as the gate is approved, opt the workflow into auto-resume at the top level of the workflow file:

name: gated-deploy
autoResume: true
jobs:
# ...

Or turn it on for every workflow that declares no inputs by starting serve with --auto-resume:

swamp serve --auto-resume

A workflow with autoResume: false never auto-resumes, and a workflow that declares inputs auto-resumes only with autoResume: true.

Warning

Do not set autoResume: true on a workflow whose steps after the gate expect a value passed with --input at resume. An auto-resume supplies no new inputs, so those steps run with the original run inputs.

Auto-resume only follows an approval made through serve: the dashboard, or swamp workflow approve --server. The approve command then prints:

Approved step "production-gate" in workflow "gated-deploy"
Serve is resuming run "7ffa1968-db04-4c4f-bf87-a1bd2c640222" automatically

A local swamp workflow approve still needs swamp workflow resume. For the full rules, see Auto-resume.

Supply a secret when resuming

When a step after the gate needs a credential that does not exist until the gate is cleared — an auth key minted during review, a one-time token — store it in a vault while the run is suspended and have the step read it from there. Do not pass the secret itself with --input, --input-file, or --stdin on resume: resume values are persisted like any run input (see Resume inputs).

Declare the vault key as an input and read the secret with vault.get in the step after the gate:

inputs:
  type: object
  properties:
    authKeyName:
      type: string
jobs:
  - name: rollout
    steps:
      # ... deploy-staging and production-gate as above ...
      - name: deploy-production
        dependsOn:
          - step: production-gate
            condition:
              type: succeeded
        task:
          type: model_method
          modelType: command/shell
          modelName: production-deploy
          methodName: execute
          inputs:
            run: ./deploy.sh --auth-key ${{ vault.get(deploy-secrets, inputs.authKeyName) }}

The run starts without authKeyName; it is resolved only when deploy-production runs. In a command/shell command, leave the vault.get expression unquoted: the secret is passed as a quoted environment variable reference, which single quotes would keep literal.

After approving, mint the credential and store it under a key for this run. The prompt hides the value:

swamp vault put deploy-secrets gated-deploy-auth-7ffa1968

Then resume with only the key name, and delete the secret once the run has finished:

swamp workflow resume gated-deploy --input authKeyName=gated-deploy-auth-7ffa1968
swamp vault delete deploy-secrets gated-deploy-auth-7ffa1968 --force

The step's data shows the secret as ***. A per-run key keeps two suspended runs of the same workflow from overwriting each other's credential. If only one run can be suspended at a time, read a fixed key such as vault.get(deploy-secrets, auth-key) instead; the resume then needs no input, so it also works from the dashboard and with auto-resume.

Against a swamp serve instance, swamp vault put --server takes the value as an argument rather than a prompt, and the caller needs permission to write to the vault. See Vaults for the flags.

If you followed an earlier version of this guide and passed a secret with --input at resume, rotate it. The value was recorded in the evaluated model definition under .swamp/definitions-evaluated/, in the data of models that record their arguments, and in child runs it was passed to.

Hand off approval to someone else

The person who runs a workflow and the person who approves its gate need not be the same. Anyone with repo access can list pending gates with swamp workflow approvals and approve or reject them; the identity that decides is recorded as decidedBy on the gate. After a colleague approves, you (or they) run swamp workflow resume to continue.

Expire a stale gate

Set timeout (seconds) on the gate so an approval that arrives too late is refused rather than silently shipping a change nobody is still watching:

task:
  type: manual_approval
  prompt: "Staging verified. Approve production rollout?"
  timeout: 2

Once the window elapses, approving fails and the gate stays unapproved:

Error: 'Approval timed out: step "production-gate" has been waiting 5s (timeout: 2s)'

An expired gate also drops out of swamp workflow approvals, so once the deadline passes that listing comes back empty — that is expected, not a sign the run is lost. Re-run the workflow to open a fresh approval window.

Reference

For the full field list, status values, and the approvalDecision record, see the workflows reference. For how suspend and resume work under the hood, see Understanding Workflow Suspension.