Expressions & data flow
Any string field in a job — a command, a url, header values, a body, instructions, and
env values — can contain {{ }} expressions. They’re evaluated at run time, just before
the job executes, so they always see fresh values: the current time, the latest parent output,
the resolved secrets.
url: 'https://api.example.com/stats?key={{ $secrets.api_key }}&day={{ $now.date }}'In the editor, every expression-aware field shows a small {} badge — click it to insert a
secret, variable, or one of the macros below at the cursor. A field can mix literal text with
several {{ }} segments; each result is stringified and spliced into the surrounding text.
What you can reference
| Expression | What it is |
|---|---|
$vars | Global non-secret variables ({{ $vars.region }}) |
$secrets | Resolved secret values for this job ({{ $secrets.api_key }}) — masked in logs |
$credentials | Reusable credentials by name ({{ $credentials.myapi.token }}) — secret fields masked |
$env | The daemon’s environment minus its own secrets ({{ $env.HOME }}) |
$now | The current time, with date helpers ({{ $now.date }}) |
$parent | The upstream (parent) run’s output — how data flows between jobs (below) |
$parents | Each parent’s latest output by id ({{ $parents['fetch-stats'].json.total }}) |
Data flow: $parent
When a job runs after a parent (an afterParent schedule point, or as an onFailure
handler), it receives the parent run’s output as $parent — this is how one job hands data to
the next:
| Field | What it holds |
|---|---|
{{ $parent.text }} | the parent’s captured text output |
{{ $parent.json }} | the parent’s output parsed as JSON ($parent.json.total) |
{{ $parent.status }} | an http parent’s response status code |
{{ $parent.error }} | the failure message when the parent failed — see Failure handling |
{{ $parent.name }} / {{ $parent.jobId }} | the parent job’s name / id |
$parent is populated even for a manual or scheduled run of a child: it falls back to the
parent’s latest run matching the point’s required outcome (default succeeded), so “Run now” on
a chained job still sees its parent’s last output. Operators like switch and wait pass their
own parent’s output straight through, so a fetch → wait 2d → child still reads the fetch data.
A worked example
# jobs/reports/fetch-stats.yaml — the parent: fetch JSON on a schedule
id: fetch-stats
name: Fetch stats
group: reports
type: http
method: GET
url: 'https://api.example.com/stats?key={{ $secrets.api_key }}'
schedule:
- { kind: daily, time: '07:00' }# jobs/reports/post-summary.yaml — the child: runs after the parent succeeds, uses its JSON
id: post-summary
name: Post summary
group: reports
type: slack
credential: slack-bot
channel: '#reports'
message: 'Today: {{ $parent.json.total }} events (fetched {{ $now.date }})'
schedule:
- { kind: afterParent, parent: fetch-stats, on: succeeded }Secrets vs. $env
{{ }} is a templating convenience, not a secret store. $env deliberately drops the
daemon’s own secrets — anything named CRONABLE_* or ANTHROPIC_*, and any key containing
KEY, TOKEN, SECRET, PASSWORD, or CREDENTIAL — so {{ $env.CRONABLE_ENCRYPTION_KEY }}
renders empty, never the value. For any secret an expression genuinely needs, use
{{ $secrets.X }} (encrypted at rest, masked everywhere) or a credential. See
Security.
The code job type shares this sandbox
Expressions run in a node:vm context seeded with only the helpers above, with a source
denylist (rejecting constructor, process, require, eval, Function, …), code
generation disabled, and a per-expression timeout. Each segment’s result is capped at 256 KB so a
runaway expression can’t clog a command, log, or the live stream. A code job’s script runs
through the same guard over the same context — it just returns a value that becomes the run’s
structured output for the next job.
This is a best-effort guard for a trusted, single-operator tool, not a hardened security boundary — it runs JavaScript you wrote, in the same trust boundary as the commands the daemon already executes on your behalf. Never feed attacker-controlled strings into an expression field.