Skip to content

Env Var Substitution

Pipelines reference environment variables via bash-style `${VAR}` / `${VAR:-default}`.

v0.4.0concept

Environment variable substitution (v0.3+)

Pipelines reference environment variables via bash-style ${VAR} / ${VAR:-default}. Substitution runs before yaml.load (text preprocessing), so ${VAR} works inside any string position — top-level scalars, array elements, nested map values, multi-line block scalars. Inspired by 12-factor (bash/zsh) convention.

Syntax

Pattern env present env absent
${VAR} env value throws MissingEnvVarError
${VAR:-default} env value (default ignored) default value
${VAR:-} env value (incl. empty string) empty string

Var-name regex: [A-Z_][A-Z0-9_]* — uppercase letter or underscore start, then uppercase letters / digits / underscores. Lowercase first char not matched (left literal as ${lowercase}). Single-pass substitution (not recursive).

3-layer defense (where substitution fires)

Layer Entry-point Error response
1. validate CLI agentsmarket validate pipeline.yaml exit 1: ✗ Environment variable "X" is required (no default specified)
2. run CLI agentsmarket run pipeline.yaml exit 1: same text
3. server publish-pipeline POST /v1/pipelines HTTP 400 { code: 'MISSING_ENV_VAR', var: 'X', error }

The /v1/pipelines/:id/invoke route does not re-substitute — spec_yaml stored in D1 is already substituted at publish-time, and re-substituting at invoke would be wrong (env may have changed between publish and invoke).

Where env vars come from

  • validate / run CLI: process.env of the CLI process (shell, .env, Docker -e, CI secrets)
  • Server publish-pipeline: server’s process.env at request-time (never trust caller-provided secrets)
  • Hosted operator runs: hosted runner env

Examples (5 example pipelines demonstrate)

All 5 examples declare at least one ${VAR:-default} literal so they remain runnable without any env set; override env to customize tone/persona:

parse_logs.system: ${BUG_TRIAGE_TONE:-You are an SRE on-call engineer. Triage with speed and precision.}

analyze.system: ${EXTRA_GUIDANCE:-You are a senior code reviewer. Identify improvement opportunities. Be concise and specific.}

per_article_summary.system: ${NEWS_READER_TONE:-You write for a busy professional. Skip boilerplate, name tradeoffs, lead with new facts.}

per_review.system: ${FOCUS_AREA:-You analyze customer reviews for overall satisfaction, themes, and concrete improvement signals.}

analyze.system: ${ANALYSIS_DEPTH:-You analyze source code for issues, complexity, and lines of code.}

Pre-existing interpolation patterns (not affected)

Pattern Engine Example
{{var}} stage-input interpolation prompt: "Hello {{name}}"
$input.X input literal prompt: "$input.user_query"
$stages.X.output stage-output chaining prompt: "Prev: $stages.analyse.output"

These use different syntax (double-brace for inputs, single-dollar-no-brace for input/stage references) and do not conflict with ${VAR}.

JSON output mode (validate CLI)

{
  "ok": false,
  "error": "Environment variable \"MISSING_VAR\" is required (no default specified)",
  "var": "MISSING_VAR"
}

Behavioral oracle (tests = ground truth for documented behavior)

  • packages/pipeline-runtime/tests/resolve-vars.test.ts — R7.1 (13) + R7.6 (6): substitution unit-tests covering arrays / nested maps / block scalars / special chars in defaults / empty defaults / recursive throw across YAML tree.
  • packages/pipeline-runtime/tests/examples.test.ts — R7.5 (11): example pipelines declare ${VAR:-default}, parse with empty env, env-override propagates into stage.
  • packages/cli/tests/validate.test.ts — R7.2: validate CLI fail-fast on missing var.
  • packages/cli/tests/run-registry.test.ts — R7.3: run CLI fail-fast + env-override visible in startup log.
  • packages/server/tests/publish-pipeline-r6.test.ts — R7.4: server 400 MISSING_ENV_VAR.

If this doc contradicts tests → tests are the source of truth (PR a fix to this page).