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

Relationships

#3221 workflow recover misjudges whether an unknown step is guarded: forEach iterations read as unguarded, and a same-named step in another job is matched

Opened by zack · 10/9/2026· Shipped 10/9/2026

Description

assessRecoveryForRun (src/domain/workflows/recovery_assessment.ts) decides whether an interrupted run can be recovered without operator acknowledgement by checking that every step left in unknown status has a guard. It finds each step's definition by the run step's name alone, across all jobs:

const step = workflow.jobs.flatMap((j) => j.steps).find((s) => s.name === stepName);

That lookup is wrong in two ways.

1. A guarded step expanded by forEach reads as unguarded

A forEach iteration is stored on the run under its expanded name (for example deploy-prod), which no step of the definition has, so the lookup misses and the step is counted as unguarded. Recovery is refused and asks for acknowledgement even though the step it was expanded from has a guard. The step run does record the name of the step it was expanded from (forEachTemplate), which is not consulted.

2. An unguarded step is treated as guarded when another job has a guarded step of the same name

run.unknownSteps() returns step names without their job, and the lookup takes the first step of that name in any job. With job a holding a guarded step deploy and job b holding an unguarded step deploy, an unknown deploy in job b is reported as guarded and canAutoRecover is true. This direction skips the operator acknowledgement that an unguarded unknown step is meant to require.

Steps to reproduce

Found by reading the code during swamp-club#3218 and confirmed in-process by calling assessRecoveryForRun on interrupted runs built with WorkflowRun.fromData (not yet reproduced through the CLI, which needs a run interrupted mid-step):

Case canAutoRecover guardedSteps unguardedSteps
guarded plain step deploy-prod unknown true deploy-prod (none)
same step expanded by forEach, unknown false (none) deploy-prod
unguarded deploy in job b unknown, guarded deploy in job a true deploy (none)

Expected

  • A forEach iteration is judged by the guard of the step it was expanded from.
  • A step is looked up in its own job, so the third case reports deploy as unguarded and refuses automatic recovery.

Notes

Same root cause family as swamp-club#3217 and swamp-club#3218: a run step resolved against the definition by name. Callers are swamp workflow recover (src/cli/commands/workflow_recover.ts) and WorkflowExecutionService.assessRecovery.

Environment

  • swamp at main 8d880ec7 plus the swamp-club#3218 branch (which does not touch this file)
  • Linux x86_64
02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 7 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/9/2026, 6:38:49 PM

Click a lifecycle step above to view its details.

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

Sign in to post a ripple.