Skip to content

Two incompatible env-variable expansion syntaxes coexist silently across agent yaml fields #2615

@tdabasinskas

Description

@tdabasinskas

docker-agent has two completely different env-variable expansion mechanisms, each used in a different subset of yaml fields. They use incompatible syntaxes (${env.X} vs $X / ${X}), neither errors when the wrong syntax appears in the wrong field, and there is no documentation that maps fields to the syntax they support. Users who infer the syntax from one example apply it to a similar-looking field and watch their config silently no-op.

The two mechanisms

JavaScript template literals via Goja

  • Syntax: ${env.VAR} (JS member access on a dynamic env object), plus ${tool({...})} and arbitrary JS expressions.
  • Used in: agents.<name>.description, agents.<name>.welcome_message, agents.<name>.commands, toolsets[*].env values.

Shell-style env expansion via os.ExpandEnv

  • Syntax: $VAR or ${VAR} (POSIX-ish, no env. prefix), plus ~ for home directory.
  • Used in: toolsets[*].working_dir, toolset path resolution.

What this looks like to a user

Field What works What silently fails
agents.<name>.description ${env.X} $X (literal)
agents.<name>.welcome_message ${env.X} $X (literal)
agents.<name>.commands ${env.X} $X (literal)
agents.<name>.instruction (nothing — #2614) both
toolsets[*].env (values) ${env.X} $X (literal)
toolsets[*].instruction (nothing — #2614) both
toolsets[*].working_dir $X or ${X} ${env.X} (literal as path component)
Toolset path refs $X or ${X} ${env.X} (literal as path component)

A user who learns from the description: ${env.HOME} example will reasonably write working_dir: ${env.HOME}/work and end up with a directory literally named ${env.HOME}. A user who learns from working_dir: $HOME/work will write description: $HOME and the model will see the literal $HOME.

Neither failure mode logs anything. The runtime accepts both yamls, loads the agent, and proceeds. A fast-fail at load (or a clear log line) would turn each instance into a 30-second fix. The current behavior turns it into a debugging session.

Suggestions

  1. Unify on a single syntax. The JS template literal expander is the more powerful one (env access, tool calls, arbitrary expressions) and is already used in more places. Replace the os.ExpandEnv call sites in pkg/path/expand.go with the JS expander. Treat ~ as a separate path-only concern. This eliminates the dual-syntax problem entirely.

  2. Make both syntaxes work everywhere. Run shell-style expansion as a pre-pass before the JS expander, or vice versa. Costs: more surface area, harder to reason about precedence, edge cases when a string legitimately contains $X. Possible but messy.

If none of those is acceptable, at minimum: add the matrix to the docs.

Metadata

Metadata

Assignees

Labels

area/configFor configuration parsing, YAML, environment variableseffort:mediumMultiple files or components, some design decisions neededpriority:highMajor impact, should be addressed within 2 days

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions