How to Iterate Over Variable Sets in Probe Steps: A Complete Guide

Use the iteration field in Probe steps to run the same action multiple times with different variable sets, referencing each iteration's data via {{vars.key}} templates.

Probe, the lightweight workflow automation tool by linyows/probe, provides a powerful mechanism to execute steps repeatedly across different datasets without duplicating YAML definitions. By leveraging the iteration field, you can parameterize steps with variable sets that override default values and execute sequentially during job runtime.

Understanding the Iteration Mechanism in Probe

The iteration system relies on two core components: the Step struct definition that stores variable sets, and the job execution logic that processes these sets sequentially.

The Step Struct and Iteration Field

In step.go at line 26, the Step struct defines the Iteration field as a slice of maps, where each map represents a distinct variable set:

type Step struct {
    // ... other fields ...
    Iteration []map[string]any `yaml:"iteration"`   // holds the variable sets
    // ... other fields ...
}

Each map in the Iteration slice contains key-value pairs that override the step's default vars during that specific execution cycle.

Job Execution Logic

The job controller in job.go (lines 80-84) determines whether to enter iteration mode based on the presence of variable sets:

if len(st.Iteration) == 0 {
    j.executeStep(st, &idx, ctx, nil)          // normal execution
} else {
    j.executeStepWithIterations(st, &idx, ctx) // enter iteration mode
}

When iterations exist, the system calls executeStepWithIterations instead of the standard step executor.

Implementing Variable Set Iteration in Your Probe Configuration

Basic YAML Syntax for Iteration

Define the iteration field as a list of maps under any step. Each map represents one execution cycle with its specific variable values. The following example from examples/iteration-literal.yml demonstrates the complete syntax:

jobs:
- name: Run iteration example
  steps:
  - name: "4 times iterations: {{vars.name}}"
    uses: shell
    with:
      cmd: echo "ID={{vars.id}} Name={{vars.name}} Role={{vars.role}}"
    test: |
      res.code == 0 && indexOf(res.stdout, vars.name) >= 0
    echo: "{{vars}}"
    vars:                     # default values (used if a key is missing in an iteration)

      id: 1234
      name: default
      role: admin
    iteration:
    - {id: 2000, name: foo, role: user}
    - {id: -3000, name: bar, role: editor}
    - {id: 0.123, name: baz, role: guest}
    - {id: "5000", name: qux, role: guest}

This configuration executes the shell step four times, substituting {{vars.id}}, {{vars.name}}, and {{vars.role}} with the values from each iteration map.

Using Default Variables with Iteration Sets

The vars field at the step level defines default values that apply when an iteration map omits specific keys. In examples/iteration-literal.yml, the default id: 1234 would only apply if an iteration entry lacked the id key. This merging behavior ensures backward compatibility while allowing selective overrides:

vars:
  id: 0          # fallback if an iteration does not provide `id`

  role: viewer   # fallback role

iteration:
  - {name: Alice, id: 101}  # Uses id: 101, role: viewer

  - {name: Bob}             # Uses id: 0, role: viewer

Conditional Execution with skipif and Iteration Variables

Iteration variables are accessible in conditional expressions such as skipif. This allows you to skip specific iterations based on their variable values:

steps:
- name: Conditional step
  uses: shell
  with: {cmd: "echo hi"}
  skipif: vars.name == "skip-me"
  iteration:
    - {name: "run-me"}
    - {name: "skip-me"}

In this example, the second iteration is omitted because the skipif condition evaluates to true when vars.name equals "skip-me".

How Probe Processes Iteration Internally

The executeStepWithIterations Function

Located in job.go at lines 96-100, the executeStepWithIterations function handles the actual looping logic:

func (j *Job) executeStepWithIterations(st *Step, idx *int, ctx *JobContext) {
    for _, vars := range st.Iteration {
        j.executeStep(st, idx, ctx, vars)      // each vars becomes the step's "vars"
    }
}

This function iterates over the st.Iteration slice, passing each variable map to executeStep as the vars parameter. The step index (idx) is incremented once per iteration, ensuring each execution appears as a distinct entry in the final report.

Context Merging in SetCtx

Before each iteration executes, the Step.SetCtx method (in step.go, lines 20-34) prepares the execution context by merging job-level variables with the current iteration's override map:

func (st *Step) SetCtx(j JobContext, override map[string]any) {
    // ... setup evalCtx with job vars and outputs ...
    evalCtx := StepContext{
        Vars:        j.Vars,
        Outputs:     outputs,
        RepeatIndex: j.RepeatCurrent,
    }
    // ... evaluate step-level vars using the iteration map (override) ...
    st.ctx = StepContext{Vars: merged, Outputs: outputs, ...}
}

The override parameter contains the current iteration's variable map. This map is merged with the step's default vars, allowing iteration-specific values to take precedence while preserving defaults for unspecified keys.

Template Evaluation

During execution, templates within with, test, echo, and other step fields are evaluated against the prepared context. The vars namespace provides access to the merged variables:

  • {{vars.id}} – Accesses the iteration's id value
  • {{vars.name}} – Accesses the name variable
  • {{vars.role}} – Accesses the role variable

Each iteration evaluates templates independently, ensuring that conditional logic and output formatting reflect the current variable set.

Summary

  • Use the iteration field in Probe steps to execute the same action multiple times with different variable sets, defined as a list of maps in your YAML configuration.
  • Reference iteration variables in templates using the {{vars.key}} syntax within with, test, echo, and skipif expressions.
  • Leverage default values by defining a vars map at the step level; iteration maps override specific keys while unspecified keys fall back to defaults.
  • Understand the execution flow: Job.executeSteps detects iterations and calls executeStepWithIterations, which loops through variable sets and invokes Step.SetCtx to merge contexts before each run.
  • Track individual executions: Each iteration increments the step index independently, producing distinct entries in the final execution report.

Frequently Asked Questions

What is the iteration field in Probe steps?

The iteration field is a YAML array of maps defined within a step that enables data-driven testing by running the same step multiple times with different inputs. Each map in the array represents one execution cycle, with its key-value pairs overriding the step's default variables for that specific run. According to the linyows/probe source code in step.go, this field is parsed into []map[string]any and processed by the job executor when present.

How do I reference iteration variables in templates?

Iteration variables are accessed using the {{vars.key}} template syntax within step fields such as with, test, echo, and skipif. For example, if an iteration map contains {name: "Alice", id: 101}, you can reference these values as {{vars.name}} and {{vars.id}}. The Step.SetCtx method in step.go merges these iteration-specific values into the step context before template evaluation, ensuring each iteration uses its own variable set.

Can I use default values with iteration sets?

Yes, you can define default variables using the vars field at the step level. When an iteration map omits a specific key, the step falls back to the value defined in vars. For example, if vars defines role: viewer and an iteration entry only specifies {name: "Bob"}, the step will use role: viewer for that execution. This merging behavior is handled in step.go during context preparation, allowing you to specify only the values that differ across iterations while maintaining consistent defaults.

How does Probe handle step indexing with iterations?

Each iteration is treated as a distinct step execution for reporting purposes. The executeStepWithIterations function in job.go increments the step index (idx) once per iteration when calling executeStep. This means if a step has four iteration entries, the final report will contain four separate entries (e.g., steps 1, 2, 3, 4) rather than a single aggregated result. This granular indexing allows you to identify exactly which variable set produced specific outputs or failures in the execution report.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →