for-each
for-each - execute a mini state machine for each item in a list
SYNOPSIS
Section titled “SYNOPSIS”state-name: tool: for-each arguments: items-from: <command> on-empty: <continue|fail> on-iteration-error: <continue|stop> start-at: <first-state> states: <state-name>: tool: <tool> arguments: ... next: <next-state>DESCRIPTION
Section titled “DESCRIPTION”The for-each tool iterates over a list of items (one per line) from a command’s output and executes a nested state machine for each item. This enables processing multiple files, records, or values without writing shell loops.
Within the loop body, two special variables are available:
{{ item }}- The current item value{{ index }}- The zero-based iteration index
The nested states support the same features as top-level states: tool execution, output capture, goto branching, and error handling.
ARGUMENTS
Section titled “ARGUMENTS”| Argument | Required | Default | Description |
|---|---|---|---|
items-from | Yes | - | Shell command that outputs items (one per line) |
on-empty | No | continue | Behavior when no items: continue (skip) or fail |
on-iteration-error | No | stop | Behavior on error: stop (abort) or continue |
NESTED STATE STRUCTURE
Section titled “NESTED STATE STRUCTURE”The states block defines a mini state machine. Each state supports:
| Field | Description |
|---|---|
tool | The tool to execute |
arguments | Tool arguments (with variable substitution) |
output | Output capture (var(name) or file(path)) |
next | Next state to transition to |
goto | Conditional branching based on output |
on-error | Error handling with specific exit codes |
EXIT CODES
Section titled “EXIT CODES”| Code | Meaning |
|---|---|
0 | All iterations completed successfully |
1 | items-from command failed, empty list with on-empty: fail, or iteration error with on-iteration-error: stop |
-1 | Workflow paused during iteration |
EXAMPLES
Section titled “EXAMPLES”Process Files
Section titled “Process Files”process-files: tool: for-each arguments: items-from: find ./src -name "*.ts" -type f start-at: lint-file states: lint-file: tool: bash arguments: command: eslint "{{ item }}"Process with Index
Section titled “Process with Index”rename-files: tool: for-each arguments: items-from: ls *.jpg start-at: rename states: rename: tool: bash arguments: command: mv "{{ item }}" "photo_{{ index }}.jpg"Multi-step Processing
Section titled “Multi-step Processing”deploy-services: tool: for-each arguments: items-from: cat services.txt start-at: build states: build: tool: bash arguments: command: docker build -t "{{ item }}:latest" "./{{ item }}" next: push
push: tool: bash arguments: command: docker push "{{ item }}:latest" next: deploy
deploy: tool: bash arguments: command: kubectl rollout restart deployment/{{ item }}Conditional Processing
Section titled “Conditional Processing”process-branches: tool: for-each arguments: items-from: git branch -r --format='%(refname:short)' start-at: check-branch states: check-branch: tool: if-equal arguments: left: "{{ item }}" right: "origin/main" goto: true: skip-main false: process-branch
skip-main: tool: bash arguments: command: echo "Skipping main branch"
process-branch: tool: bash arguments: command: echo "Processing branch {{ item }}"Error Handling Per Iteration
Section titled “Error Handling Per Iteration”migrate-databases: tool: for-each arguments: items-from: cat databases.txt on-iteration-error: continue start-at: migrate states: migrate: tool: bash arguments: command: ./migrate.sh "{{ item }}" on-error: _: log-error
log-error: tool: bash arguments: command: echo "Failed to migrate {{ item }}" >> errors.logCapture Output Per Item
Section titled “Capture Output Per Item”analyze-repos: tool: for-each arguments: items-from: cat repos.txt start-at: get-stats states: get-stats: tool: bash arguments: command: 'cd "{{ item }}" && git log --oneline | wc -l' output: var(commit_count) next: report
report: tool: bash arguments: command: echo "{{ item }}: {{ commit_count }} commits"Empty List Handling
Section titled “Empty List Handling”process-errors: tool: for-each arguments: items-from: grep -l "ERROR" logs/*.log 2>/dev/null || true on-empty: continue start-at: analyze states: analyze: tool: bash arguments: command: echo "Analyzing {{ item }}"RESTRICTIONS
Section titled “RESTRICTIONS”- The
items-fromcommand runs in a non-interactive shell - Interactive tools cannot be used within the loop states
- The
pausetool can be used but will pause the entire workflow - Variables set inside the loop are available after the loop completes (last value wins)
COMMON PATTERNS
Section titled “COMMON PATTERNS”Batch Processing
Section titled “Batch Processing”batch-process: tool: for-each arguments: items-from: "ls data/*.json | head -100" start-at: process states: process: tool: bash arguments: command: ./process-json.sh "{{ item }}"Parallel Alternative
Section titled “Parallel Alternative”For independent operations, consider using parallel instead:
# Sequential with for-eachprocess-sequential: tool: for-each arguments: items-from: echo -e "a\nb\nc" start-at: task states: task: tool: bash arguments: command: ./slow-task.sh "{{ item }}"
# Parallel alternative (faster but unordered)process-parallel: tool: parallel tasks: task-a: tool: bash arguments: command: ./slow-task.sh a task-b: tool: bash arguments: command: ./slow-task.sh b task-c: tool: bash arguments: command: ./slow-task.sh c