YAML Configuration Reference
This page documents every key Probe reads from a workflow file, the expression context each field is evaluated in, and the validation rules that apply.
Workflow Structure
The skeleton below shows every property a workflow file can carry and where each one belongs.
name: string # Required: workflow name
description: string # Optional: what the workflow does
vars: # Optional: workflow variables
key: value
secrets: # Optional: environment variables to hide in output
- NAME
jobs: # Required: a list of jobs
- name: string # Required: job name
id: string # Optional: job id, used by needs
needs: [job-id, ...] # Optional: job dependencies
skipif: expression # Optional: skip the job when true
defaults: # Optional: default `with` values per action
http:
url: string
repeat: # Optional: run the job repeatedly
count: integer
interval: duration
steps: # Required: a list of steps
- name: string # Optional: step name
id: string # Optional: step id, required to publish outputs
uses: string # Required: action name
with: # Optional: action parameters
key: value
test: expression # Optional: assertion
echo: string # Optional: text added to the report
vars: # Optional: step variables
key: value
outputs: # Optional: values published for later steps
key: expression
skipif: expression # Optional: skip the step when true
wait: duration # Optional: wait before running the step
timeout: duration # Optional: step timeout
iteration: # Optional: run the step once per entry
- key: value
retry: # Optional: retry on failure
max_attempts: integer
interval: duration
initial_delay: durationjobs is a list, not a mapping. A workflow that writes jobs: as a mapping of job ids fails to load.
There is no top-level env key and no top-level defaults key. Environment variables are read through vars, and defaults belongs to a job.
Top-Level Properties
Four properties sit at the root of a workflow file alongside jobs: what to call it, what it does, the values it starts with, and which of those values must not be shown.
name
Type: String (required)
Description: Name of the workflow, shown at the top of the report.
name: "API Health Check"description
Type: String (optional)
Description: Longer explanation of what the workflow does.
description: |
Checks the production API:
- endpoint availability
- response timesvars
Type: Object (optional)
Description: Variables available to every job and step as vars.<name>.
vars is the only place where environment variables are visible, and they are referenced by their bare name. Values are evaluated once, before the first job starts.
vars:
# Read an environment variable
api_url: "{{API_URL}}"
# With a fallback
timeout: "{{REQUEST_TIMEOUT ?? '30s'}}"
# Computed at load time
run_id: "{{random_str(8)}}"
# Nested values are supported
auth:
user: "{{API_USER}}"Expressions inside a step cannot read environment variables directly - there is no env in the expression context. Put the variable in vars and read vars.<name>.
A var can read another var as vars.<name>, wherever the other one is written in the block. Each var is evaluated after the vars it reads, so a value computed once, such as a random password, is the same in every var built from it:
vars:
http_port: "{{HTTP_PORT ?? '18080'}}"
base: "http://localhost:{{vars.http_port}}"
password: "{{PASSWORD ?? random_str(24)}}"
basic_auth: "Basic {{encode_base64('alice:' + vars.password)}}"A var that reads itself, directly or through other vars, stops the workflow before the first job starts with an error naming the chain, such as vars: circular reference: a -> b -> a. A name that is not a var reads as nil, so vars.<name> ?? 'default' gives the default. A var that reads vars by a key known only when it runs, as vars[KEY], is evaluated after all the others. A var that calls template, such as one built from a template file, is evaluated after all the vars that do not, since what the template reads is known only when it runs. When the template reads another var that calls template and has not been evaluated yet, that var is evaluated first, and a cycle between them stops the workflow as one between other vars does.
The templates in a list are evaluated as those in a map are, at any depth, so ports: ["{{vars.http_port}}"] holds the port.
A var whose template cannot be evaluated, such as {{nothing.here}}, which reads a field of nil, stops the workflow before the first job starts with exit status 2. The error names the var and the template, as vars.a: {{nothing.here}}: cannot fetch here from <nil>. A var that reads one that failed is not evaluated, so its error is not repeated.
secrets
Type: Array of strings (optional)
Description: Names of environment variables whose values must not appear in anything Probe prints or writes.
secrets:
- API_TOKEN
- DB_PASSWORD
vars:
api_token: "{{API_TOKEN}}"
db_password: "{{DB_PASSWORD}}"Wherever the value of a listed variable would appear, Probe writes <secret:NAME> instead. That covers the report on the terminal, step names, echo, the request and response shown for a failed step, --verbose output, the log records of actions, and the files written by --report. The value is also hidden in the escaped forms it takes when it is quoted in an error message or encoded as JSON. Steps, tests and outputs still see the real value; only what is shown changes.
A name that is not set, or set to an empty string, has nothing to hide and is skipped. A short value is replaced wherever it appears, so a secret such as 1 would hide every 1 in the output.
Independently of secrets, the values of the Authorization, Proxy-Authorization, Cookie and Set-Cookie headers are always shown as <redacted>. A token obtained while the workflow runs, such as one returned by a login step and sent in a later request, is never listed in secrets, but it travels in one of these headers. Probe learns these values as an action is about to send or has received them, and hides them from then on, including in the action’s own log records.
The same goes for credentials passed to an action: the value of any password or key_passphrase field, at any depth of with, is shown as <redacted>, and so is the password in a database URL such as the dsn of the db action, while the rest of the URL stays visible. A password written straight into a step therefore does not appear in --verbose output or an action’s log records, even though it is not listed in secrets. When the template of such a value cannot be evaluated, the error names the value, as with.password, but does not say why, since the template and the error may each quote the credential.
Jobs
jobs is a map from job name to job definition. A job groups the steps that run in sequence, and declares what it depends on.
Job Properties
A job definition takes the properties below.
| Property | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Job name, shown in the report. Supports template expressions |
id | String | No | Identifier used by another job’s needs. Generated automatically when omitted |
needs | Array | No | Ids of jobs that must finish first |
steps | Array | Yes | The steps to run |
skipif | Expression | No | Skip the whole job when the expression is true |
defaults | Object | No | Default with values, keyed by action name |
repeat | Object | No | Run the job repeatedly |
There is no if, continue_on_error or timeout at the job level.
needs
Jobs without dependencies start in parallel. needs refers to job ids, so a job that others depend on needs an explicit id.
jobs:
- id: setup
name: Setup
steps:
- name: Prepare
uses: hello
echo: "ready"
- name: Test
needs: [setup]
steps:
- name: Run
uses: hello
echo: "testing"skipif
A boolean expression. The job is skipped when it evaluates to true.
jobs:
- name: Production only
skipif: vars.environment != "production"
steps:
- name: Check
uses: http
with:
method: GET
url: "{{vars.api_url}}/health"defaults
Default parameters merged into the with of every step in the job that uses the matching action. A value set on the step wins.
jobs:
- name: API checks
defaults:
http:
url: "{{vars.api_url}}"
headers:
authorization: "Bearer {{vars.token}}"
accept: application/json
steps:
- name: Health
uses: http
with:
get: /health # resolved against the default url
test: res.code == 200The http action requires a method. Either set method explicitly, or use the shorthand key get / post / put / delete / patch, whose value is a full URL or a path resolved against url.
- name: Explicit method
uses: http
with:
url: "{{vars.api_url}}/health"
method: GETrepeat
repeat runs the whole job again on an interval, which is what turns a test into a monitor.
| Property | Type | Required | Description |
|---|---|---|---|
count | Integer | Yes | Number of runs. Capped by PROBE_MAX_REPEAT_COUNT (default 10000) |
interval | Duration | No | Wait between runs |
async | Boolean | No | Run the repetitions concurrently |
jobs:
- name: Poll until ready
repeat:
count: 10
interval: 5s
steps:
- name: Check
uses: http
with:
method: GET
url: "{{vars.api_url}}/status"
test: res.code == 200Each run’s steps read the outputs that the earlier steps of the same run published, also when async runs them at the same time. The jobs after it read each output as the latest run that published it left it, so a step the last run skipped keeps the value of the run before.
Steps
A step is one action invocation plus what surrounds it: the condition that decides whether it runs, the test that decides whether it passed, and the outputs it leaves for later steps.
Step Properties
A step definition takes the properties below.
| Property | Type | Required | Description |
|---|---|---|---|
uses | String | Yes | Action to run: a built-in such as http or shell, or an external action |
name | String | No | Step name, shown in the report |
id | String | No | Identifier that namespaces this step’s outputs |
with | Object | No | Action parameters |
test | Expression | No | Assertion. The step fails when it is false |
echo | String | No | Text added to the report |
vars | Object | No | Variables local to the step |
outputs | Object | No | Values published for later steps and jobs |
skipif | Expression | No | Skip the step when true |
wait | Duration | No | Wait before running the step |
timeout | Duration | No | Step timeout (default 5m) |
iteration | Array | No | Run the step once per entry, exposed as vars |
retry | Object | No | Retry the step on failure |
The key is uses, not action, and the conditional key is skipif, not if.
outputs
Values published for use by later steps and jobs. A step only publishes outputs when it has an id - without one the outputs block is discarded.
Each value is an expression, not a template, so it is written without {{ }}.
steps:
- name: Log in
id: auth
uses: http
with:
url: "{{vars.api_url}}/login"
method: POST
test: res.code == 200
outputs:
token: res.body.access_token
user_id: res.body.user.idLater steps read them either namespaced by step id or by the output name alone:
headers:
Authorization: "Bearer {{outputs.auth.token}}"
X-User: "{{outputs.user_id}}"An id containing a hyphen is not a valid identifier in an expression, so it has to be read with brackets:
echo: "{{outputs['create-user'].user_id}}"retry
retry repeats a single step until its test passes or the attempts run out.
| Property | Type | Required | Description |
|---|---|---|---|
max_attempts | Integer | Yes | Total attempts, at least 1. Capped by PROBE_MAX_ATTEMPTS (default 10000) |
interval | Duration | No | Wait between attempts |
initial_delay | Duration | No | Wait before the first attempt |
steps:
- name: Flaky endpoint
uses: http
with:
method: GET
url: "{{vars.api_url}}/slow"
test: res.code == 200
retry:
max_attempts: 3
interval: 2siteration
Runs the step once per entry. Each entry’s keys are exposed through vars.
steps:
- name: Check {{vars.path}}
uses: http
iteration:
- path: /health
- path: /metrics
- path: /version
with:
method: GET
url: "{{vars.api_url}}{{vars.path}}"
test: res.code == 200The Expression Context
Inside a step, an expression sees exactly these names:
| Name | Type | Description |
|---|---|---|
vars | Object | Workflow variables merged with the step’s own vars |
res | Object | The action’s response |
req | Object | The request as it was sent |
rt | Object | Response time: rt.duration (string) and rt.sec (float seconds) |
status | Integer | Action exit status, 0 on success |
outputs | Object | Outputs published by earlier steps |
repeat_index | Integer | Current index when the job repeats |
There is no env, jobs or steps in this context.
The res Object for http
Each action defines its own res. For http it holds the following.
| Field | Type | Description |
|---|---|---|
res.code | Integer | Status code, such as 200 |
res.status | String | Status line, such as "200 OK" |
res.headers | Object | Response headers, keyed by canonical name such as Content-Type |
res.body | Any | Response body. Parsed into an object or array when the response is JSON, otherwise the raw string |
res.rawbody | String | The unparsed body, present when the body was parsed as JSON |
test: |
res.code == 200 &&
res.headers["Content-Type"] contains "application/json" &&
res.body.status == "ok" &&
rt.sec < 1Other actions publish their own res fields; see the Actions Reference.
Data Types
Two kinds of value in a workflow file have their own syntax: durations, and the expressions and templates that read the context.
Duration
A duration is either a Go duration string or a plain number of seconds.
timeout: "30s"
timeout: "5m"
interval: 10 # 10 secondsExpressions and Templates
A template expression appears inside a string and is replaced by its value:
url: "{{vars.api_url}}/users/{{outputs.auth.user_id}}"A boolean expression is written bare, without braces:
test: res.code == 200 && rt.sec < 2
skipif: vars.environment == "local"outputs values are expressions too, so they take no braces.
A template that cannot be evaluated is an error, never text in the value. A name that is not defined reads as nil and is not an error, but reading a field of nil is, as is an expression that does not parse:
| Where the template is | What happens |
|---|---|
A step’s with, vars or name | The step fails with the kind template, and the action does not run. The error names the value, as with.headers.authorization |
A workflow’s vars | The workflow stops before the first job, with exit status 2 |
A job’s name | The job fails, with exit status 2 |
A step’s echo | The error is shown in place of the text, and the step’s result does not change |
# Fails the step when no earlier step published outputs.login
headers:
authorization: "Bearer {{outputs.login.token}}"
# Falls back instead
headers:
authorization: "Bearer {{outputs.login?.token ?? 'none'}}"See Built-in Functions for what can be called inside an expression.
Validation Rules
nameis required at the workflow level, andjobsmust be a non-empty list.- Every job needs a
nameand at least one step. - Every step needs a
uses. - A job’s
needsmust refer to ids that exist, and the dependency graph must be acyclic. repeat.countmust be zero or greater, andretry.max_attemptsat least 1.- A step must have an
idfor itsoutputsto be published.
probe check reports these without running the workflow, together with keys a workflow does not take, expressions that do not parse and outputs read before they are published. See the CLI Reference.
File Merging
Several files can be combined by passing them comma-separated. They are concatenated in order, and a top-level key defined more than once takes the value from the last file:
probe base.yml,production.ymlSee File Merging for the merge rules.
See Also
- CLI Reference - Command-line options
- Actions Reference - Action parameters and responses
- Built-in Functions - Expression functions
- Environment Variables - Variables Probe reads