Common Mistakes
What you will learn
- The recurring mistakes that cause workflows to fail validation or behave unexpectedly
- Why each mistake is tempting, and where the wrong habit usually comes from
- The correct Zigflow construct to use instead
This page assumes the data model from Data and Expressions and Data Flow. The data-flow mistakes below are summarised here and explained in full on those pages.
Most invalid workflows break the same handful of rules. The mistakes below
come from habits carried over from other tools: the CNCF Serverless Workflow
specification, GitHub Actions, Jinja templating or the jq command line.
Zigflow implements a deliberately small, strict subset and rejects anything
outside it, so a habit that works elsewhere often fails here.
1. Invalid task types and call shapes
A task's type is the single task key its body contains. Zigflow supports
exactly eleven task types: call, do, for, fork, listen, raise,
run, set, switch, try and wait. There is no http, parallel,
function, delay or emit task.
Why people get this wrong. Other workflow engines and the wider Serverless
Workflow ecosystem expose task types like http or parallel. In Zigflow an
HTTP request is a call, concurrency is a fork and a delay is a wait.
Incorrect: an http: task
do:
- fetchUser:
http:
url: https://api.example.com/users/2
Correct: a call: http task
do:
- fetchUser:
call: http
with:
method: get
endpoint: https://api.example.com/users/2
Use this mapping when a task type from elsewhere does not exist in Zigflow:
| Habit from elsewhere | Zigflow equivalent |
|---|---|
http: task | call: http |
function: task | call: activity |
parallel: task | fork |
delay: task | wait |
emit: task | no equivalent, not supported |
url instead of endpoint
An HTTP call addresses its target with endpoint, not url. There is no
url property. The name comes from the Serverless Workflow specification, but
most HTTP tooling calls it a URL, so url is a frequent slip.
# Incorrect: url is not a recognised field, endpoint is missing
with:
method: post
url: https://api.example.com/orders
# Correct
with:
method: post
endpoint: https://api.example.com/orders
Unsupported constructs are rejected, not ignored. Zigflow validates a workflow
before it runs and fails with messages such as unsupported task type or
unsupported call type. A feature being present in the CNCF Serverless
Workflow specification does not mean Zigflow implements it. When in doubt,
check the task reference.
2. Expression syntax and runtime variables
Runtime expressions are jq (via gojq) wrapped in
${ ... }. A string without ${ } is a literal value, not an expression.
Why people get this wrong. Every templating tool uses a different wrapper.
GitHub Actions uses ${{ }}, Jinja and Handlebars use {{ }}, and the jq
command line uses a bare . or $. path. None of these work in Zigflow.
Incorrect: the wrong wrappers
set:
a: '{{ $input.name }}' # Jinja or Handlebars
b: '${{ $input.name }}' # GitHub Actions
c: '$.input.name' # bare jq path
Correct: ${ ... }
set:
greeting: ${ $input.name }
Invented runtime variables
Only five variables exist in an expression: $context, $data, $env,
$input and $output. Names such as $workflow, $task, $steps, $vars
or $now do not exist and fail at evaluation.
There is no $now. Use ${ timestamp } or ${ timestamp_iso8601 }, and only
inside a set task, so the value is stable across
Temporal replays. See section 5 below.
Map the common invented variables to the real ones:
| Invented variable | Use instead |
|---|---|
$workflow, $workflow.id | $data.workflow, $data.workflow.workflow_execution_id |
$task, $steps | $output for the last task, or $data.<taskName> by name |
$vars | $context for exported data, $data for workflow state |
$now | ${ timestamp } or ${ timestamp_iso8601 } inside a set task |
3. Misreading the data-flow model
These four mistakes are the same root confusion between Zigflow's two data channels. Data Flow explains the model with worked examples. This is the short version.
- Confusing
exportandoutput.output.asshapes$output, the result that flows to the next task and that the workflow returns.export.aswrites to$context, which persists for later tasks. They are different channels. - Assuming
exportmerges into$context. Eachexport.asreplaces$contextwholesale. To add to it without losing earlier values, merge explicitly with${ $context + { ... } }. - Reading
$outputafter intervening tasks.$outputonly holds the most recent task's result. To use a value several steps later,exportit to$context. - Expecting outputs to flow through a task's input. A task does not
automatically receive the previous task's result as its working input. Chain
results explicitly through
$output, or read a named result from$data.<taskName>.
$data and $context are not the same. $data accumulates across tasks and
is never replaced, but how a task writes to it depends on the task type. A
set task merges its fields directly, so a set that defines requestId is
read as $data.requestId. An activity-backed task stores its result under the
task name, so an HTTP task named fetchUser is read as $data.fetchUser.
$context is written only by export and is replaced on each export. A
$data key is only readable after the task that wrote it has run.
4. Durations
A duration is an object with plural integer keys. The valid keys are days,
hours, minutes, seconds and milliseconds. ISO 8601 strings such as
PT1M are not the supported format, and singular keys such as minute are not
recognised.
Why people get this wrong. ISO 8601 durations appear throughout the Serverless Workflow specification and many standards, and singular keys read naturally in English.
Incorrect: ISO 8601 and singular keys
do:
- pauseIso:
wait: PT1M # ISO 8601 string, not supported
- pauseSingular:
wait:
minute: 1 # singular key, rejected by validation
Correct: plural keys
do:
- pause:
wait:
minutes: 1
seconds: 30
Unknown duration keys are validation errors. A typo such as minute is
rejected because wait durations only support the documented plural keys. Always
use the plural keys exactly. See the wait task.
5. Non-deterministic values in the wrong place
Generated values such as ${ uuid }, ${ timestamp } and
${ timestamp_iso8601 } must be produced inside a set
task and referenced later through $data.
Why people get this wrong. It is natural to drop a ${ uuid } straight into
an HTTP body or a wait expression. Temporal replays workflow history, so a
value generated outside a recorded side effect changes on replay and raises a
non-determinism error.
Incorrect: generated inside a call
do:
- callApi:
call: http
with:
method: post
endpoint: https://api.example.com/orders
body:
requestId: ${ uuid } # regenerated on replay
Correct: generated in a set task
do:
- generateId:
set:
requestId: ${ uuid } # recorded as a side effect
- callApi:
call: http
with:
method: post
endpoint: https://api.example.com/orders
body:
requestId: ${ $data.requestId }
A wait expression rejects these functions outright. Zigflow refuses to
register a workflow whose wait duration uses uuid, timestamp or
timestamp_iso8601. Compute the value in a preceding set task and reference
the result. See Data and Expressions for
the full determinism rules.
6. Workflow document structure
The document object is closed, the do list has a fixed shape, and several
fields have strict formats. These are caught at validation time.
Unknown document fields
document rejects unknown fields. There is no name, description or
author. Put a human-readable name in title, prose in summary and anything
else under metadata or tags.
# Incorrect
document:
name: My Workflow
description: Does things
dsl: 1.0.0
taskQueue: zigflow
workflowType: my-workflow
version: 0.0.1
# Correct
document:
dsl: 1.0.0
taskQueue: zigflow
workflowType: my-workflow
version: 0.0.1
title: My Workflow
summary: Does things
The do list shape
do is an array of single-key maps, each { stepName: { task } }. It is not a
map of steps, and a step must contain exactly one task key.
# Incorrect: do as a map
do:
stepOne:
set: { a: 1 }
stepTwo:
set: { b: 2 }
# Correct: do as a list of single-key maps
do:
- stepOne:
set: { a: 1 }
- stepTwo:
set: { b: 2 }
Invalid names and versions
Zigflow requires taskQueue and workflowType to be RFC 1123 DNS labels:
letters, digits and hyphens, starting and ending with a letter or digit. No
underscores, dots or spaces. This is a Zigflow validation rule, not a Temporal
one. dsl and version must be semantic versions.
# Incorrect
taskQueue: my_queue.v2
# Correct
taskQueue: my-queue-v2
Quick reference
| Instead of | Use |
|---|---|
http:, parallel:, function:, delay:, emit: tasks | call: http, fork, call: activity, wait |
url: | endpoint: |
{{ }}, ${{ }}, $.foo | ${ ... } |
$workflow, $task, $steps, $vars, $now | $data, $output, $context, $data.workflow |
PT1M, minute: 1 | minutes: 1 |
${ uuid } in a call or wait | ${ uuid } in a set task, then $data |
document.name, document.description | document.title, document.summary |
do as a map | do as a list of single-key maps |
Related pages
- Data Flow:
$output,$context,exportandoutput - Data and Expressions: expression syntax, variables and determinism
- Tasks: introduction: the eleven task types
- Call task: HTTP, gRPC and activity calls
- Wait task: durations and the
untilform - DSL reference: the full
documentand task schema