Relationships
#3218 manual_approval timeout is never enforced on a forEach-expanded gate
Opened by zack · 10/9/2026· Shipped 10/9/2026
Description
A manual_approval step expanded by forEach never expires. Its timeout is ignored by swamp workflow approve, swamp workflow reject and swamp workflow approvals: the gate stays in the pending list and can be approved long after its deadline. The same gate without forEach expires as documented.
Found while triaging swamp-club#3217, which shares the root cause but is a different symptom.
Steps to reproduce
Two workflows that differ only in forEach, each with timeout: 1 on the gate:
jobs:
- name: main
steps:
- name: approve-${{ self.env }}
forEach:
item: env
in: "${{ [inputs.env] }}"
task:
type: manual_approval
prompt: "Deploy?"
timeout: 1Run each with --input env=prod, wait about three seconds, then:
swamp workflow approvals --json
swamp workflow approve <workflow> approve-prod --run <run-id>Actual (forEach gate)
approvals lists approve-prod under pending, nothing under expired, and approve succeeds:
Approved step "approve-prod" in workflow "foreach-input-gate-t"Expected (what the plain gate does)
approvals lists the step under expired, and approve refuses:
Error: Approval timed out: step "approve-prod" has been waiting 3s (timeout: 1s)Cause
Every reader of a gate's timeout finds the task by looking the waiting step up by name in the workflow definition:
src/libswamp/workflows/approve.tsandreject.tssrc/libswamp/workflows/approvals.tssrc/domain/workflows/nested_run_link.tssuspendedEventForinsrc/domain/workflows/execution_service.ts
A forEach-expanded step is stored on the run under its expanded name (approve-prod), which is not a step name in the definition, so the lookup misses and evaluateApprovalTimeout returns undefined: no deadline. The step run records the gate's prompt (approvalPrompt) but not its timeout, so there is nothing else to read it from.
A likely fix is to record the timeout on the step run when the gate is requested, beside approvalPrompt, and have the readers prefer it. The same miss also leaves timeout off the suspended event and off approvalRequired in workflow run --json.
Impact
An approval deadline is a control: a gate meant to lapse after N seconds stays approvable indefinitely when the step is expanded by forEach. Consider whether this should be typed as security.
Environment
- swamp 20261009.170507.0-sha.d8a46694
- Linux x86_64
- Scratch repo: /tmp/swamp-repro-issue-3217 (workflows foreach-input-gate-t and plain-gate-t)
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.