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-deployYou 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-eea982cc08b1Each 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 approvalsworkflow·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-eea982cc08b1A 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-eea982cc08b1If 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-deployResume 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 UTCIf 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-f6e3557027a6Run 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-f6e3557027a6The 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-e4a7778efa0esystem │ 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-b1ad3dde52b3workflow·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-084ec84695d4Run 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:
- Open
/dashboard/approvalsand click Approve (or Reject) on the gate. - 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-resumeA 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" automaticallyA 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-7ffa1968Then 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 --forceThe 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: 2Once 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.