Skip to content

feat(task): allow passing arguments to task dependencies via {{usage.*}} templates#8893

Merged
jdx merged 8 commits intomainfrom
feat/dep-args
Apr 4, 2026
Merged

feat(task): allow passing arguments to task dependencies via {{usage.*}} templates#8893
jdx merged 8 commits intomainfrom
feat/dep-args

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 4, 2026

Summary

  • Task dependencies can now reference parent task arguments using {{usage.*}} templates in depends, depends_post, and wait_for
  • Arguments flow through dependency chains (A -> B -> C) — each task parses its own usage spec and re-renders child dependency templates with resolved values
  • Works with both positional args (arg) and flags (flag), and with both string and structured dependency syntax

Example

[tasks.build]
usage = 'arg "<app>"'
run = 'echo "building {{usage.app}}"'

[tasks.deploy]
usage = 'arg "<app>"'
depends = [{ task = "build", args = ["{{usage.app}}"] }]
run = 'echo "deploying {{usage.app}}"'
$ mise run deploy myapp
[build] building myapp
[deploy] deploying myapp

Closes discussion #4331

Implementation

Dependency templates containing {{usage.*}} are deferred during initial config loading (since CLI args aren't available yet). They are re-rendered later with actual usage values at two points:

  1. Top-level tasks — after CLI args are resolved in run.rs, before dependency graph construction
  2. Dependency chain tasks — during Deps::new() graph building, when a task receives args from its parent

Key changes:

  • Task stores raw (unrendered) dependency templates in depends_raw/depends_post_raw/wait_for_raw
  • New render_depends_with_usage() method re-renders deps with a usage context
  • New parse_usage_values_from_task() parses a task's usage spec against its args to extract named values
  • Dependency resolution (resolve_depends, all_depends) skips deps with unresolved {{usage.*}} refs

Test plan

  • E2E test test_task_dep_args covering:
    • Basic arg passing to dependency
    • Flag passing to dependency
    • Multiple parallel dependencies receiving the same arg
    • String syntax (depends = ["child {{usage.name}}"])
    • Dependency chaining (A -> B -> C, args flow through all levels)
  • All existing dep-related e2e tests pass (dep_env, deps, run_depends, depends_post, depends_post_multiple, deps_circular, skip_deps, failure_hang_depends, delegation_dedup, double_dash_behavior, args_position)
  • All 563 unit tests pass

🤖 Generated with Claude Code


Note

Medium Risk
Touches task dependency resolution and graph building to defer and later re-render dependency specs, which can affect what tasks run and with what args across chains. Added e2e coverage reduces risk but dependency parsing/rendering changes can have edge cases (e.g., skip_deps, mixed template/env syntax).

Overview
Enables {{usage.*}} templates inside depends, depends_post, and wait_for so a task can forward its resolved usage args/flags to dependency tasks (including through multi-level dependency chains).

Implements deferred rendering for dependency entries that reference {{usage.*}} by preserving raw dependency templates on Task, skipping unresolved deps during initial resolution, then re-rendering them later once CLI/parent-provided args are known (during mise run task list creation and again while building the dependency graph).

Adds an end-to-end test covering positional args, flags, string vs structured dependency syntax, fan-out dependencies, and chained forwarding, and updates docs to describe the new argument-forwarding behavior.

Reviewed by Cursor Bugbot for commit 076b9c5. Bugbot is set up for automated code reviews on this repo. Configure here.

…*}} templates

Task dependencies can now reference parent task arguments using {{usage.*}}
templates. This enables patterns like:

  [tasks.deploy]
  usage = 'arg "<app>"'
  depends = [{ task = "build", args = ["{{usage.app}}"] }]
  run = 'echo "deploying {{usage.app}}"'

Running `mise run deploy myapp` will pass "myapp" to the build dependency.
Arguments flow through dependency chains (A -> B -> C) and work with both
positional args and flags.

Closes discussion #4331

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 4, 2026

Greptile Summary

This PR introduces {{usage.*}} template interpolation inside depends, depends_post, and wait_for declarations, allowing parent task arguments to flow into their dependency tasks. The implementation uses a two-phase rendering strategy: dependencies with {{usage.*}} refs are deferred during initial config loading (in Task::render()), then resolved at runtime either from CLI args (in run.rs) or when a parent task passes args to a child (in Deps::new()). A new depends_raw / depends_post_raw / wait_for_raw trio stores the original templates so they can be re-rendered with the correct usage context later.

Key behaviors:

  • dep_has_usage_ref() correctly strips whitespace before pattern-matching {{usage. to handle Tera's {{ usage.foo }} style without false positives
  • parse_usage_values_from_task() parses a task's usage spec against its current args and emits a {name → value} map; keys are derived by stripping the usage_ env-var prefix from po.as_env() output
  • Dependency chain propagation (A→B→C) is handled in Deps::new() by checking depends_raw for usage refs when a task has non-empty args, then calling render_depends_with_usage before graph edges are added
  • String-syntax deps like \"build {{usage.app}}\" work because TaskDep::render() already splits a fully-rendered multi-token task string into a task name + args via shell_words
  • E2E test covers positional args, flags, parallel fan-out, string syntax, and three-level chaining

Confidence Score: 5/5

Safe to merge — implementation is correct, prior review concerns have been addressed, and only minor P2 style suggestions remain.

All P0/P1 concerns from previous review threads have been resolved: dep_has_usage_ref now correctly strips whitespace and matches {{usage. as a unit (no false positives), debug logging was added on parse failure, and the has_usage_deps guard properly inspects raw dep lists rather than just checking Option::is_some(). The two remaining findings are P2: a double-rendering of non-usage deps that is functionally a no-op in all realistic cases, and a mildly fragile echo-based test assertion. The e2e coverage is thorough (positional args, flags, parallel fan-out, string syntax, three-level chain) and all 563 unit tests pass.

src/task/mod.rs (render_depends_with_usage re-renders all deps from raw); e2e/tasks/test_task_dep_args (echo "$output" assertion pattern)

Important Files Changed

Filename Overview
src/task/mod.rs Core of the feature: adds depends_raw/depends_post_raw/wait_for_raw fields, deferred rendering in render(), new render_depends_with_usage() method, dep_has_usage_ref() helper, and parse_usage_values_from_task(). Logic is sound; minor concern around rendering all deps (not just usage ones) from raw in render_depends_with_usage.
src/task/deps.rs Adds usage-template re-rendering to Deps::new() before resolving edges; correctly checks raw dep lists for {{usage.*}} before calling parse_usage_values_from_task.
src/cli/run.rs Adds a pre-Deps loop that re-renders top-level task deps with CLI-resolved usage values; straightforward integration with existing task_list flow.
e2e/tasks/test_task_dep_args New E2E test covers positional args, flags, parallel deps, string syntax, and three-level chaining. Uses a slightly fragile pattern (assert_contains "echo "$output"") for parallel-task output capture.
docs/tasks/task-configuration.md New documentation section for parent-to-dependency argument forwarding with clear examples for args, flags, string syntax, and chain propagation.
docs/tasks/task-arguments.md Adds a cross-reference paragraph pointing users to the new depends-forwarding section; minimal, correct change.

Sequence Diagram

sequenceDiagram
    participant CLI as mise run deploy myapp
    participant Run as run.rs
    participant TaskList as get_task_lists()
    participant Mod as Task::render()
    participant RDU as render_depends_with_usage()
    participant Deps as Deps::new()
    participant Resolve as resolve_depends()

    CLI->>Run: task="deploy", args=["myapp"]
    Run->>TaskList: get_task_lists(skip_deps=false)
    TaskList->>Mod: task.render() for each task
    Note over Mod: Saves depends_raw/depends_post_raw/wait_for_raw<br/>Defers deps with {{usage.*}} — renders rest
    Mod-->>TaskList: tasks with deferred usage deps
    TaskList-->>Run: task_list=[deploy(args=["myapp"])]

    Run->>Run: For each task with args in task_list
    Run->>Mod: parse_usage_values_from_task(deploy)<br/>→ {"app": "myapp"}
    Run->>RDU: deploy.render_depends_with_usage({"app":"myapp"})
    Note over RDU: Restores from depends_raw<br/>Renders "build {{usage.app}}" → task="build", args=["myapp"]
    RDU-->>Run: deploy.depends = [build(args=["myapp"])]

    Run->>Resolve: resolve_depends(task_list)
    Resolve-->>Run: resolved_tasks

    Run->>Deps: Deps::new(resolved_tasks)
    loop For each task popped from stack
        Deps->>Deps: has_usage_deps(task.depends_raw)?
        alt task has args AND raw usage deps
            Deps->>Mod: parse_usage_values_from_task(build)<br/>→ {"app": "myapp"}
            Deps->>RDU: build.render_depends_with_usage({"app":"myapp"})
        end
        Deps->>Resolve: task.resolve_depends() — filters remaining {{usage.*}} deps
        Deps->>Deps: add edges to graph
    end
    Deps-->>Run: dependency graph built
Loading

Fix All in Claude Code

Reviews (3): Last reviewed commit: "[autofix.ci] apply automated fixes (atte..." | Re-trigger Greptile

Add documentation for the new {{usage.*}} template support in task
dependencies, with examples for positional args, flags, string syntax,
and dependency chaining. Cross-reference from task-arguments.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enables passing arguments and flags from a parent task to its dependencies using {{usage.*}} templates. It implements deferred rendering for dependency templates containing usage references, ensuring they are resolved only after the parent task's arguments are available. The changes include updates to the task model to store raw templates, logic in the CLI and dependency resolver to trigger re-rendering, and a new E2E test suite. Review feedback suggests simplifying the Tera context insertion by leveraging direct serialization and broadening the template reference detection to support a wider range of Tera syntax.

src/task/mod.rs Outdated
Comment on lines +1140 to +1143
let usage_ctx: HashMap<String, tera::Value> = usage_values
.iter()
.map(|(k, v)| (k.clone(), tera::Value::String(v.clone())))
.collect();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The manual conversion of usage_values to a HashMap<String, tera::Value> is unnecessary. Tera's Context::insert can directly handle any type that implements serde::Serialize, and IndexMap<String, String> already does. This simplification avoids redundant allocations and cloning.

Suggested change
let usage_ctx: HashMap<String, tera::Value> = usage_values
.iter()
.map(|(k, v)| (k.clone(), tera::Value::String(v.clone())))
.collect();
tera_ctx.insert("usage", usage_values);

src/task/mod.rs Outdated

/// Check if a TaskDep contains {{usage.*}} references that need deferred rendering.
fn dep_has_usage_ref(dep: &TaskDep) -> bool {
let has_ref = |s: &str| s.contains("{{") && s.contains("usage.");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The heuristic for detecting usage references in dependency templates is a bit narrow. It currently only checks for {{ tags and dot notation (e.g., usage.app). Tera also supports statement tags ({% ... %}) and bracket notation (e.g., usage['app']). Expanding this check will make the deferred rendering more robust for complex templates.

Suggested change
let has_ref = |s: &str| s.contains("{{") && s.contains("usage.");
let has_ref = |s: &str| {
(s.contains("{{") || s.contains("{%") || s.contains("{#"))
&& (s.contains("usage.") || s.contains("usage["))
};

jdx and others added 3 commits April 4, 2026 15:04
- Tighten dep_has_usage_ref to strip whitespace and check for
  "{{usage." as a single substring (avoids false positives)
- Log debug message when usage parse fails instead of silently
  swallowing the error
- Check if raw deps actually contain usage refs before calling
  parse_usage_values_from_task (avoids unnecessary work)
- Simplify tera context insertion using direct serialize

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…errors

Wrap inline {{usage.*}} mentions with <span v-pre> so VitePress
doesn't try to parse them as Vue template interpolations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3f32496. Configure here.

jdx and others added 3 commits April 4, 2026 15:24
- Check depends_post_raw and wait_for_raw (not just depends_raw) when
  deciding whether to re-render deps with usage values in Deps::new
- Don't restore deps from raw templates if they were cleared by
  skip_deps, preventing unintended dependency execution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jdx jdx merged commit 45d8a36 into main Apr 4, 2026
35 checks passed
@jdx jdx deleted the feat/dep-args branch April 4, 2026 15:48
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 4, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.3 x -- echo 22.4 ± 0.4 21.6 24.9 1.00
mise x -- echo 22.9 ± 0.5 22.1 26.5 1.02 ± 0.03

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.3 env 22.0 ± 0.7 21.1 27.1 1.00
mise env 22.7 ± 0.6 21.7 26.0 1.03 ± 0.04

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.3 hook-env 22.8 ± 0.5 21.8 24.8 1.00
mise hook-env 23.4 ± 0.6 22.3 25.7 1.03 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.3 ls 19.8 ± 0.4 19.1 21.9 1.00
mise ls 20.6 ± 0.5 19.5 23.3 1.04 ± 0.03

xtasks/test/perf

Command mise-2026.4.3 mise Variance
install (cached) 149ms 149ms +0%
ls (cached) 79ms 78ms +1%
bin-paths (cached) 83ms 83ms +0%
task-ls (cached) 806ms 804ms +0%

jdx pushed a commit that referenced this pull request Apr 5, 2026
### 🚀 Features

- **(ci)** auto-convert external PRs to draft mode by @jdx in
[#8896](#8896)
- **(deps)** add `depends` field for user-specified tool dependencies by
@cprecioso in [#8776](#8776)
- **(dotnet)** support runtime-only installs by @fragon10 in
[#8524](#8524)
- **(npm)** apply install_before to transitive dependencies by @risu729
in [#8851](#8851)
- **(task)** allow passing arguments to task dependencies via
{{usage.*}} templates by @jdx in
[#8893](#8893)
- add options field to BackendListVersionsCtx by @esteve in
[#8875](#8875)

### 🐛 Bug Fixes

- **(backend)** filter PEP 440 .dev versions in fuzzy version matching
by @richardthe3rd in [#8849](#8849)
- **(ci)** update COPR BuildRequires rust version to match MSRV 1.88 by
@jdx in [#8911](#8911)
- **(ci)** add Ruby build dependencies to e2e Docker image by @jdx in
[#8910](#8910)
- **(ci)** add missing build dependencies to e2e Docker image by @jdx in
[#8912](#8912)
- **(ci)** add missing build dependencies to e2e Docker image by @jdx in
[#8914](#8914)
- **(ci)** use Node 24 LTS for corepack e2e test by @jdx in
[#8915](#8915)
- **(ci)** add libxml2 and pkg-config to e2e Docker image by @jdx in
[#8917](#8917)
- **(ci)** add libxml2-dev to e2e image and disable Swift SPM tests by
@jdx in [#8918](#8918)
- **(docs)** use sans-serif font for badges by @jdx in
[#8887](#8887)
- **(env)** parse --env=VALUE and -E=VALUE flag forms correctly by @jdx
in [#8889](#8889)
- **(exec)** use i64::from() for seccomp syscall numbers to survive
autofix by @jdx in [#8882](#8882)
- **(github)** preserve tool options like filter_bins when version
specified via CLI by @jdx in
[#8888](#8888)
- **(github)** use alias-specific options when tool_alias has its own
config by @jdx in [#8892](#8892)
- **(install)** add locked_verify_provenance setting and detect github
attestations at lock time by @jdx in
[#8901](#8901)
- **(lock)** prune stale version entries during filtered `mise lock
<tool>` runs by @altendky in
[#8599](#8599)
- **(python)** use lockfile URL for precompiled installs by @hehaoqian
in [#8750](#8750)
- **(release)** verify all build targets succeed before releasing by
@jdx in [#8886](#8886)
- **(ruby)** support build revisions for precompiled binaries in
mise.lock by @jdx in [#8900](#8900)
- **(swift)** fall back to Ubuntu 24.04 for unsupported Ubuntu versions
by @jdx in [#8916](#8916)
- **(zsh)** avoid duplicate trust warning after cd by @timothysparg in
[#8898](#8898)
- update flake.lock and add fix for rust-bindgen to default.nix by
@esteve in [#8874](#8874)
- when direnv diff is empty, do not try to parse it by @yaleman in
[#8857](#8857)
- skip trust check for plain .tool-versions in task list by @dportalesr
in [#8876](#8876)

### 🚜 Refactor

- **(go)** rename go_* settings to go.* namespace by @jdbruijn in
[#8598](#8598)

### 📚 Documentation

- **(tasks)** clarify task_config.includes behavior by @risu729 in
[#8905](#8905)

### 🧪 Testing

- **(ci)** run e2e tests inside Docker containers by @jdx in
[#8899](#8899)

### 📦️ Dependency Updates

- bump ubi from 0.8 to 0.9 by @jdx in
[#8906](#8906)
- bump zip from 3 to 8 by @jdx in
[#8908](#8908)
- update lockfile deps (hold back rattler) by @jdx in
[#8909](#8909)
- update bun.lock by @jdx in
[#8913](#8913)

### 📦 Registry

- add turso
([github:tursodatabase/turso-cli](https://github.com/tursodatabase/turso-cli))
by @kenn in [#8884](#8884)
- remove carp test by @jdx in
[#8894](#8894)

### Chore

- **(ci)** add workflow to warn PRs modifying vendored aqua-registry by
@jdx in [#8897](#8897)
- **(ci)** use github.token for draft conversion in auto-draft workflow
by @jdx in [#8903](#8903)
- remove deprecated settings older than 12 months by @jdx in
[#8904](#8904)

### New Contributors

- @dportalesr made their first contribution in
[#8876](#8876)
- @timothysparg made their first contribution in
[#8898](#8898)
- @hehaoqian made their first contribution in
[#8750](#8750)
- @jdbruijn made their first contribution in
[#8598](#8598)
- @cprecioso made their first contribution in
[#8776](#8776)
- @yaleman made their first contribution in
[#8857](#8857)
- @kenn made their first contribution in
[#8884](#8884)
- @fragon10 made their first contribution in
[#8524](#8524)

## 📦 Aqua Registry Updates

#### New Packages (6)

- [`ahkohd/oyo`](https://github.com/ahkohd/oyo)
- [`bellicose100xp/jiq`](https://github.com/bellicose100xp/jiq)
- [`kurama/dealve-tui`](https://github.com/kurama/dealve-tui)
- [`micahkepe/jsongrep`](https://github.com/micahkepe/jsongrep)
- [`textfuel/lazyjira`](https://github.com/textfuel/lazyjira)
- [`ubugeeei/vize`](https://github.com/ubugeeei/vize)

#### Updated Packages (1)

- [`sigstore/cosign`](https://github.com/sigstore/cosign)
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.

1 participant