Skip to Content
ReferenceYAML Configuration

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: duration

jobs 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 times

vars

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.

PropertyTypeRequiredDescription
nameStringYesJob name, shown in the report. Supports template expressions
idStringNoIdentifier used by another job’s needs. Generated automatically when omitted
needsArrayNoIds of jobs that must finish first
stepsArrayYesThe steps to run
skipifExpressionNoSkip the whole job when the expression is true
defaultsObjectNoDefault with values, keyed by action name
repeatObjectNoRun 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 == 200

The 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: GET

repeat

repeat runs the whole job again on an interval, which is what turns a test into a monitor.

PropertyTypeRequiredDescription
countIntegerYesNumber of runs. Capped by PROBE_MAX_REPEAT_COUNT (default 10000)
intervalDurationNoWait between runs
asyncBooleanNoRun 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 == 200

Each 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.

PropertyTypeRequiredDescription
usesStringYesAction to run: a built-in such as http or shell, or an external action
nameStringNoStep name, shown in the report
idStringNoIdentifier that namespaces this step’s outputs
withObjectNoAction parameters
testExpressionNoAssertion. The step fails when it is false
echoStringNoText added to the report
varsObjectNoVariables local to the step
outputsObjectNoValues published for later steps and jobs
skipifExpressionNoSkip the step when true
waitDurationNoWait before running the step
timeoutDurationNoStep timeout (default 5m)
iterationArrayNoRun the step once per entry, exposed as vars
retryObjectNoRetry 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.id

Later 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.

PropertyTypeRequiredDescription
max_attemptsIntegerYesTotal attempts, at least 1. Capped by PROBE_MAX_ATTEMPTS (default 10000)
intervalDurationNoWait between attempts
initial_delayDurationNoWait 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: 2s

iteration

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 == 200

The Expression Context

Inside a step, an expression sees exactly these names:

NameTypeDescription
varsObjectWorkflow variables merged with the step’s own vars
resObjectThe action’s response
reqObjectThe request as it was sent
rtObjectResponse time: rt.duration (string) and rt.sec (float seconds)
statusIntegerAction exit status, 0 on success
outputsObjectOutputs published by earlier steps
repeat_indexIntegerCurrent 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.

FieldTypeDescription
res.codeIntegerStatus code, such as 200
res.statusStringStatus line, such as "200 OK"
res.headersObjectResponse headers, keyed by canonical name such as Content-Type
res.bodyAnyResponse body. Parsed into an object or array when the response is JSON, otherwise the raw string
res.rawbodyStringThe 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 < 1

Other 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 seconds

Expressions 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 isWhat happens
A step’s with, vars or nameThe step fails with the kind template, and the action does not run. The error names the value, as with.headers.authorization
A workflow’s varsThe workflow stops before the first job, with exit status 2
A job’s nameThe job fails, with exit status 2
A step’s echoThe 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

  • name is required at the workflow level, and jobs must be a non-empty list.
  • Every job needs a name and at least one step.
  • Every step needs a uses.
  • A job’s needs must refer to ids that exist, and the dependency graph must be acyclic.
  • repeat.count must be zero or greater, and retry.max_attempts at least 1.
  • A step must have an id for its outputs to 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.yml

See File Merging for the merge rules.

See Also

Updated at