Relationships
#2713 forEach over a direct step with a shared modelName runs every iteration with one iteration's globalArgs on first creation
Opened by hammz · 9/29/2026· Shipped 9/29/2026
Summary
A forEach step that runs a model type directly with a fixed modelName and per-iteration globalArgs runs every iteration with one iteration's global arguments, but only on the run that first creates the auto-definition. No error or warning is raised. Method inputs are unaffected, and later runs are correct.
Reproduction (swamp 20260929.154804.0-sha.80c0526f)
Extension model @repro/echo: one global argument message, and a method record(label) that writes a resource named label holding { label, message }.
- name: record-${{ self.item }}
forEach:
item: item
in: ${{ ['a', 'b', 'c', 'd'] }}
task:
type: model_method
modelType: "@repro/echo"
modelName: echo-shared
methodName: record
globalArgs:
message: ${{ self.item }}
inputs:
label: ${{ self.item }}First swamp workflow run (the definition does not exist yet). The run succeeds, and swamp data get echo-shared <label> shows:
a -> a
b -> a
c -> a
d -> aThe same happened on 3 of 3 fresh modelNames. The winning value varies (a, b, d), so it is whichever iteration created the definition. The second run, against the existing definition, is correct (a -> a, b -> b, ...). A templated modelName: echo-${{ self.item }} is correct on every run.
Expected
Each iteration runs with its own globalArgs, as it does on later runs. Failing that, swamp should reject or warn about per-iteration globalArgs on a shared modelName.
Likely cause
In resolveOrCreateDefinition (src/libswamp/models/direct_execution.ts, the locked re-check around line 448), iterations that lose the auto-creation race adopt the winner's definition without reconciling global arguments. The comment there reads "concurrent auto-creators share the same input line, so args are identical", which does not hold for forEach iterations whose globalArgs depend on self.<item>. The existing-definition path above it does reconcile (setGlobalArgument / removeGlobalArgument), which explains why later runs are correct.
Found while working on #2492 (unrelated to it).
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.