Relationships
#2580 workflow run log output: a forEach step with a templated name labels its iterations with the raw expression, e.g. deploy-${{ self.env }}[0]
Opened by hammz · 9/28/2026· Shipped 9/29/2026
Summary
In log mode, workflow run labels each forEach iteration's lines <template name>[<index>]. When the forEach step's name is itself an expression (a pattern the swamp-uat vary-mixed and vary-cel-data-access fixtures use), the label is the unevaluated template, for example deploy-${{ self.env }}[0]. The expanded step name (deploy-dev) is known, and since swamp-club#2538 it already appears in the body of skipped lines. The label column is widened to the raw expression, so every other line of the run is indented to match.
JSON output is not affected: step records use the expanded names.
Steps to reproduce
jobs:
- name: main
steps:
- name: gate
task: { type: model_method, modelType: command/shell, modelName: label-gate, methodName: execute, inputs: { run: 'true' } }
- name: 'deploy-${{ self.env }}'
dependsOn: [{ step: gate, condition: { type: succeeded } }]
forEach: { item: env, in: '${{ ["dev","prod"] }}' }
task: { type: model_method, modelType: command/shell, modelName: label-deploy, methodName: execute, inputs: { run: 'true' } }
- name: 'rollback-${{ self.env }}'
dependsOn: [{ step: gate, condition: { type: failed } }]
forEach: { item: env, in: '${{ ["dev","prod"] }}' }
task: { type: model_method, modelType: command/shell, modelName: label-rb, methodName: execute, inputs: { run: 'true' } }
- name: 'warm-${{ self.region }}'
guard: '${{ true }}'
forEach: { item: region, in: '${{ ["us-east-1"] }}' }
task: { type: model_method, modelType: command/shell, modelName: label-warm, methodName: execute, inputs: { run: 'true' } }Observed (swamp source at ca3aa618, main)
warm-${{ self.region }}[0] │ skipped warm-us-east-1 (guarded) · guard: true
main │ step gate · label-gate · execute · start 14:19:08 UTC
main │ done gate in 37ms · 14:19:08 UTC
rollback-${{ self.env }}[0] │ skipped rollback-dev (dependency)
rollback-${{ self.env }}[1] │ skipped rollback-prod (dependency)
deploy-${{ self.env }}[0] │ step deploy-dev · label-deploy · execute · start 14:19:08 UTC
deploy-${{ self.env }}[0] │ done deploy-dev in 25ms · 14:19:08 UTC
main │ completed in 1.3s · 14:19:09 UTCIterations that run, iterations skipped by dependsOn, and guard-skipped iterations all get the raw label. It predates #2538 and #2537 (seen on 20260925.163149.0-sha.bea803c9 for running iterations).
Expected
A label a reader can match to the step, such as the expanded name (deploy-dev), or the template with its index only when the template name has no expression. A forEach step with a plain name (rollback-each[0]) keeps its current label.
Cause
WorkflowRunRenderer.getDisplayName() in src/presentation/renderers/workflow_run.ts builds the label as ${forEachTemplate}[${forEachIndex}]. runJob() in src/domain/workflows/execution_service.ts sets forEachTemplate to the key of the forEach expansion map, which is the step name as written, expression included. No renderer test covers a templated forEach name.
Found while end-to-end testing swamp-club#2537.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.