Relationships
#2455 self.globalArguments.X returns the unevaluated expression text when the global argument is itself an expression
Opened by stack72 · 9/23/2026
Description
When a global argument is itself a CEL expression, referencing it from a method argument with self.globalArguments.<field> yields the global argument's unevaluated ${{ ... }} text, not its evaluated value, even when that expression is valid. The literal text is then handed to the method.
Found while planning swamp-club#2397.
Steps to Reproduce
command/shellmodelproducerwithrun: echo hello, run it once.command/shellmodelglobalvalid:globalArguments.target: ${{ data.latest('producer', 'log').name }}- method
execute, argumentrun:echo "[${{ self.globalArguments.target }}]"
swamp model method run globalvalid execute- Output:
/bin/sh: [${{ data.latest('producer', 'log').name }}]: bad substitution, thenCommand exited with code 1. Expected:[log].
Cause (suspected)
ExpressionEvaluationService.evaluateDefinition (src/domain/expressions/expression_evaluation_service.ts) sets ctx.self.globalArguments from the original, unevaluated definition.globalArguments and evaluates every expression in the definition in a single pass. So self.globalArguments.X resolves to the raw expression string. The workflow path (src/domain/workflows/execution_service.ts, where ctx.expressionContext.self is built from originalDefinition.globalArguments before DefinitionExpressionEvaluator.evaluate) has the same shape. Literal global arguments work; only expression-valued ones are affected.
Expected Behavior
self.globalArguments.X resolves to the evaluated value of the global argument, as the swamp skill's expression reference documents (.claude/skills/swamp/references/model/references/expressions.md, examples.md).
Environment
- swamp 20260923.203315.0-sha.4ae1cfd9
- macOS
Closed
No activity in this phase yet.
stack72 commented 9/23/2026, 10:59:06 PM
Fixed in https://github.com/swamp-club/swamp/pull/2598 (merged), shipped together with swamp-club#2397.
Cause: definition evaluation built self.globalArguments from the original, unevaluated global arguments and evaluated the whole definition in a single pass. So ${{ self.globalArguments.X }} in a method argument returned X's raw ${{ ... }} source whenever X was itself an expression.
Fix: the CLI and workflow paths now share one definition pass (evaluateDefinitionExpressions) that evaluates global arguments first, then evaluates everything else with self.globalArguments bound to the result.
- A valid expression-valued global argument now resolves. The repro's
globalvalidmodel prints[log]. - A runtime (vault/env) global argument is still copied as its raw text and resolved by the runtime pass, so secret handling is unchanged.
- A global argument that fails to evaluate is copied raw and caught by the new #2397 check, so the method fails with the CEL error instead of receiving literal text.
Not covered: a global argument that references another global argument still sees the raw values (no dependency ordering between global arguments).
Tests: unit tests on both evaluators, plus a workflow-step test through the real step executor.
Sign in to post a ripple.