Skip to main content

Variables

Variables are key/value string pairs attached to a workflow or a job. They are the primary mechanism to parameterize scripts without duplicating jobs.

For predefined variables that Loom injects into every job environment (including CI_* and LOOM_*), see Predefined CI/CD variables (Loom).

If you're looking for the broader conceptual model and common pitfalls, see Concepts → Variables.

Secrets are available for sensitive values

If a value is sensitive (password, token, private key), use Secrets instead of variables. Secrets store references only, inject via file path by default, and are automatically redacted in all runtime output. See Concepts → Secrets for the mental model, or Migrating from variables to secrets for a step-by-step guide.

Syntax

Variables can be defined at three levels in the workflow YAML. For canonical schema anchors, see:

Workflow-level variables

variables:
PNPM_STORE_DIR: .pnpm-store
NODE_ENV: production

Default variables

default:
variables:
PNPM_STORE_DIR: .pnpm-store

Job-level variables

check:
stage: ci
target: linux
variables:
PNPM_STORE_DIR: .pnpm-store
script:
- echo "$PNPM_STORE_DIR"

Schema rules

The schema validator enforces:

  • variables must be a YAML mapping.
  • Variable names must match ^[A-Z_][A-Z0-9_]*$ (uppercase letters, digits, underscores; must start with a letter or underscore).
  • Values must be strings. Lists, mappings, numbers, and booleans produce a schema error.
  • A job cannot define the same key in both variables and secrets. See Syntax (v1) → secrets.

Precedence

Variables are merged before the job executes. The merge applies in layers, where later layers override earlier ones for the same key:

Priority (lowest → highest)SourceDescription
1Predefined (system) variablesCI_*, LOOM_*, and other variables resolved from the registry (pipeline, job, and step scopes).
2Workflow variablesTop-level variables and default.variables, merged into each job via the default merge process.
3Job variablesVariables defined directly on the job override workflow-level values for the same key.
4Provider overridesVariables injected by the runtime provider (Host or Docker) take final precedence.

Within layer 2, the default merge process works as follows:

  • default.variables and top-level variables both contribute default values.
  • The default mapping is merged into each job mapping before validation.
  • For variables (a mapping), keys merge recursively — the job provides overrides, the default provides fallbacks.

Merge example

version: v1
stages: [ci]

default:
target: linux
variables:
MODE: default
SHARED: from-default

print-default:
stage: ci
script: ["printf '%s\n' \"$MODE\" \"$SHARED\""]

print-override:
stage: ci
variables:
MODE: job-override
script: ["printf '%s\n' \"$MODE\" \"$SHARED\""]

Final values:

JobMODESHARED
print-defaultdefaultfrom-default
print-overridejob-overridefrom-default

print-override overrides MODE via its own variables but inherits SHARED from the default.

Provider behavior

Variables interact with providers in ways that matter for reproducibility:

Host provider

When no image: is set, the job runs on the host shell. The host provider inherits the environment of the loom run --local process, then applies workflow and job variables on top.

Recommendation: don't rely on shell environment variables that happen to be set on your machine. Put required values in workflow or job variables so runs behave identically on other machines and in CI.

See Host provider → Runtime behavior.

Docker provider

When image: is set, the job runs inside a container. The Docker provider passes merged variables into the container environment. Host environment variables are not inherited unless explicitly set via variables.

Recommendation: treat Docker jobs as isolated environments. Assume only variables declared in the workflow are available.

See Docker provider → Runtime behavior.

How to confirm the final value

Because Loom is pointer-first, you can confirm the resolved value from runtime artifacts:

  1. Print the variable from your script: printf '%s\n' "$MY_VAR"
  2. Follow the Diagnostics ladder to the step's events.jsonl that captured the stdout output.
  3. If sharing for support, start with the receipt path (see Receipts contract) and follow What to share.

Troubleshooting: precedence surprises

Use this checklist when a job sees an unexpected variable value:

  1. Validate schema first: run loom check to rule out invalid names or value types.
  2. Identify the layer:
    • Is the variable set in default.variables?
    • Is it overridden in job variables?
    • Is it coming from the host environment via the Host provider?
    • Is a predefined CI_* or LOOM_* variable shadowing your key?
  3. Confirm provider routing:
    • No image: → Host provider (inherits host env)
    • image: set → Docker provider (isolated env)
  4. Prove the runtime value: print the variable from the job script, then follow pointers to the step events.jsonl via the Diagnostics ladder.
  5. Share minimal evidence: follow What to share — receipt path, failing unit pointer, small excerpt.

Planned

  • Explicit multi-layer precedence documentation covering template extends chains.
  • Typed variables and interpolation rules.
  • Variable expansion in non-script contexts (e.g. image, cache keys).