Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneeszack

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: 1

Run 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.ts and reject.ts
  • src/libswamp/workflows/approvals.ts
  • src/domain/workflows/nested_run_link.ts
  • suspendedEventFor in src/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)
02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 7 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/9/2026, 6:01:09 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
zack assigned zack10/9/2026, 5:33:14 PM

Sign in to post a ripple.