Skip to content

fix(template): substitute tokens in command and [env] - #8

Merged
avocadoali merged 1 commit into
mainfrom
mihir/template-command-and-env
Aug 17, 2026
Merged

avocadoali merged 1 commit into
mainfrom
mihir/template-command-and-env

Conversation

@maharajamihir

Copy link
Copy Markdown
Contributor

The bug

docs/RECIPE_CONTRACT.md says template tokens are substituted in [args], [outputs.<role>.alias] and [env]. Every shipped example recipe puts them in command:

command = ["python", "scripts/train.py", "--out", "{outputs.checkpoint.dir}"]

Neither command nor [env] was actually rendered. recipe.command was cloned verbatim, and [env] values were shell-quoted as-is. So a token reaches the job as a literal string:

FileNotFoundError: [Errno 2] No such file or directory: '{outputs.result.path}/metrics.json'

Worse, it fails silently at submit time: render_value's unresolved-token guard only runs on strings that pass through it, so the documented promise —

Any leftover {...} token at submission time is a hard error — labctl will not submit a recipe with unresolved templates.

— did not hold for the two places users actually hit. The job submits happily and dies minutes (or hours) later inside their program.

I found this the slow way: three failed runs in a row while getting a first recipe working, each time assuming I'd got the token name wrong.

The examples compound it by using {outputs.<role>.dir}, which isn't in the token table at all — the code only ever defined .path.

The fix

  • render command elements and [env] values through render_value, like every other user-authored string
  • correct the three example recipes from .dir to .path
  • docs: list command as a templated site, and note that only the labctl token shape {word(.word)*} counts, so inline JSON in a bash -c command is left alone

Behaviour change worth reviewing

A recipe that previously shipped an unresolved token in command now fails at submit instead of at runtime. That's the intended behaviour and matches the documented contract, but it is a behaviour change: anything currently relying on a literal {...} reaching the job will break loudly. Given the examples themselves were broken, I judged loud failure better than silent breakage — happy to gate it behind a flag if you'd rather.

Tests

Four new unit tests: a token in command is substituted; a token in [env] is substituted; an unknown token fails at submit rather than runtime; inline JSON braces in a bash -c command survive untouched. cargo test 36 + 9 pass, cargo fmt --check clean.

Note

examples/recipes/hetjob.toml in #5 uses .dir too — I'll update that branch so the two don't conflict.

🤖 Generated with Claude Code

RECIPE_CONTRACT says tokens are substituted in `[args]`,
`[outputs.<role>.alias]` and `[env]`, and every shipped example recipe
puts them in `command`. Neither `command` nor `[env]` was actually
rendered: `recipe.command` was cloned verbatim and `[env]` values were
shell-quoted as-is, so a token like {outputs.result.path} reached the job
as a literal string and blew up inside the user's program at runtime.

render_value's unresolved-token guard never saw those strings either, so
the documented promise that "any leftover {...} token at submission time
is a hard error" did not hold for the two places users actually hit.

Render both through render_value. The examples additionally used
{outputs.<role>.dir}, which is not a token the table defines — corrected
to .path, which is what the code and the docs table agree on.

Tests cover a token in `command`, a token in `[env]`, an unknown token
failing at submit rather than at runtime, and inline JSON in a `bash -c`
command surviving untouched (find_unresolved_token only matches the
labctl token shape).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@maharajamihir
maharajamihir force-pushed the mihir/template-command-and-env branch from ff2cd94 to 13b552d Compare August 17, 2026 17:54
@avocadoali
avocadoali merged commit 8b2d175 into main Aug 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants