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

Relationships

#1837 workflow validate: does not detect guard type mismatch when ${{ inputs.X }} fills the entire field and inputs.X is non-string

Opened by mellens · 8/26/2026· Shipped 8/27/2026

Summary

swamp workflow validate passes cleanly on a workflow where a guard: field holds ${{ inputs.dryRun }} and dryRun is declared as boolean in the inputs schema. At runtime the workflow fails because swamp substitutes the actual typed boolean at parse time, producing guard: true, which fails the z.string() validation on the guard field.

The information needed to catch this is available statically: the inputs schema types dryRun as boolean, the guard field is z.string(), and the expression is a whole-field substitution (not embedded in surrounding text). Validate should flag this as an error.

Steps to reproduce

  1. Create a workflow with an input declared type: boolean (e.g. dryRun).
  2. Add a step with guard: ${{ inputs.dryRun }}.
  3. Run swamp workflow validate <name>. It outputs Result: PASSED.
  4. Run swamp workflow run <name> --input dryRun=true. The workflow fails with a type error on the guard field.

Expected

swamp workflow validate reports an error on the step: "guard received a boolean via ${{ inputs.dryRun }}; the guard field requires a string (CEL expression). Use a CEL expression that returns a boolean, e.g. ${{ data.latest(...).?field.orValue("") == "" }}."

Actual

Result: PASSED — 10 checks, all green, including "✓ Step inputs" checks that demonstrate validate does resolve the inputs schema for other purposes.

Why validate already has the information

validate already resolves the inputs schema — it checks step inputs against method signatures (e.g. "✓ Step inputs for 'ensure-repo'"). So it isn't uniformly skipping expression evaluation. For a whole-field ${{ inputs.X }} substitution, the input type is known, the field type is known, and a mismatch is statically detectable.

Workaround

Replace the boolean input reference with a data-based CEL expression that evaluates lazily (at step execution time rather than at workflow parse time). For example, instead of:

guard: ${{ inputs.dryRun }}

Use a CEL expression over a data artifact that reflects the dry-run outcome:

guard: >-
  ${{ data.latest("model", "resource-" + inputs.name).?attributes.?field.orValue("") == "" }}

This stays as a template string through parse and schema validation, then the CEL evaluator resolves it at step execution time with no type mismatch.

Context

Encountered in a Puddle repo workflow (start-development). The broken guard shipped on main and caused a partially-executed workflow run against a live GitHub repo. Fixed in commit 96b7c7f of mesgme/puddle. The false PASSED from validate was the immediate cause of the confidence that the workflow was safe to run.

swamp version

$(swamp version 2>&1 | head -1)

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNEDCLASSIFICATION+ 7 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

8/27/2026, 12:00:29 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack728/26/2026, 11:04:28 PM
Editable. Press Enter to edit.

stack72 commented 8/27/2026, 12:00:40 AM

Thanks @mellens for reporting this! The fix has been merged and a release is on its way. We appreciate your contribution to swamp.

Sign in to post a ripple.