# `${VAR}` expansion

English | [中文](env-expansion.zh.md)

[← README](../../README.md) ｜ Related: [configuration format](format.md) · [configuration sources and layers](layers.md) · [CLI `dsh-mcp`](cli.md)

`${VAR}` references (matching `\$\{[A-Za-z_][A-Za-z0-9_]*\}` anywhere in the
string) in `command`, `args[*]`, `env[*]`, `cwd`, `url` and `headers[*]` — from
**any** of the sources above — are **interpolated** from the dsh host
process environment at mount time (same semantics as Claude Code, so
`"Authorization": "Bearer ${TOKEN}"` works). An unset or empty variable makes
the row skip with an `env-missing` diagnostic naming the variable (never its
value); a literal `${NAME}` that must survive unexpanded is not expressible.
A `url` containing a reference is accepted by the pre-mount schema in any
position — including the host part, e.g. `https://${HOST}/mcp` — because
validity is judged only after expansion: expanded inputs are re-validated
against the mount schema before spawn, and a malformed result (e.g. a non-URL
`${GATEWAY}/mcp`) skips the row with an `env-invalid` diagnostic instead of
reaching the mount backend. In the snapshot/row views, `fiberPhase` stays on
the mount-lifecycle vocabulary (`pending` for a row that never mounted) and
the reason rides on a separate `skipReason` field (`env-missing` /
`env-invalid` / `config-invalid` / `plugin-throw`; other skip reasons such as
`name-taken`, `idle` and `give-up` are described in
[configuration sources and layers](layers.md)). Values are never
persisted anywhere by the plugin; the CLI writes `${VAR}` through literally,
so secrets can live in the environment while configs live in git.

From dsh 0.2, the official stdio transport builds the child environment from a
scrubbed parent (ambient names matching `/KEY|PASSWORD|SECRET|TOKEN/i` and
ambient `DSH_*` are dropped) and then overlays the configured `env`. A secret
this plugin interpolates into `env` or `headers` is explicit config and is
kept. A secret that exists only as an ambient variable, and is not written
into `env`, is no longer inherited by the child.
