fix(task): merge TOML task block into same-named file task and surface resolved dir#9147
fix(task): merge TOML task block into same-named file task and surface resolved dir#9147
Conversation
…e resolved dir When a `[tasks.<name>]` block in a config file shares a name with an auto-discovered file task (e.g. `.mise/tasks/<name>`), the TOML block is now treated as a metadata overlay: env, description, dir, aliases, depends, tools, etc. from the TOML block are merged into the file task instead of being silently discarded. Also make `mise tasks ls --json` and `mise tasks info --json` report the resolved `dir` (including any inherited `task_config.dir`) so consumers see the directory the task will actually run in, rather than `null`. Fixes the issues raised in #8673.
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to merge metadata from [tasks.<name>] TOML blocks into auto-discovered file tasks of the same name, allowing users to augment script-based tasks with environment variables, descriptions, and dependencies in mise.toml. It also updates the tasks ls --json and tasks info --json commands to report the resolved execution directory instead of the raw value. Feedback indicates that merging environment variables by simple extension may cause incorrect relative path resolution for .env files due to differing configuration sources between the script and the TOML file. Additionally, several metadata fields such as outputs, extends, and sandbox settings are currently missing from the merge logic.
| self.env.0.extend(other.env.0); | ||
| self.vars.0.extend(other.vars.0); |
There was a problem hiding this comment.
Merging env and vars by simply extending the vector will lead to incorrect resolution of relative paths in directives like _.file = ".env".
In mise, environment directives are resolved relative to the task's config_source. For a file task, this is the script's location (e.g., .mise/tasks/my-script), while for the TOML overlay, it is the mise.toml location. By moving directives from the TOML task into the file task, they will be resolved relative to the script's directory instead of the mise.toml directory, which is likely not what the user expects.
To fix this, the Task structure or the EnvResults::resolve logic might need to be updated to track the source path for each individual directive, rather than assuming a single source for the entire task.
| pub fn merge_toml_overlay(&mut self, other: Task) { | ||
| if !other.description.is_empty() { | ||
| self.description = other.description; | ||
| } | ||
| for alias in other.aliases { | ||
| if !self.aliases.contains(&alias) { | ||
| self.aliases.push(alias); | ||
| } | ||
| } | ||
| // env/vars: append so the TOML block's entries resolve after the | ||
| // file task's and take precedence on key collisions. | ||
| self.env.0.extend(other.env.0); | ||
| self.vars.0.extend(other.vars.0); | ||
| self.depends.extend(other.depends); | ||
| self.depends_post.extend(other.depends_post); | ||
| self.wait_for.extend(other.wait_for); | ||
| if other.dir.is_some() { | ||
| self.dir = other.dir; | ||
| } | ||
| if other.hide { | ||
| self.hide = true; | ||
| } | ||
| if other.raw { | ||
| self.raw = true; | ||
| } | ||
| if other.raw_args { | ||
| self.raw_args = true; | ||
| } | ||
| if other.interactive { | ||
| self.interactive = true; | ||
| } | ||
| if other.quiet { | ||
| self.quiet = true; | ||
| } | ||
| if !matches!(other.silent, Silent::Off) { | ||
| self.silent = other.silent; | ||
| } | ||
| self.sources.extend(other.sources); | ||
| if other.shell.is_some() { | ||
| self.shell = other.shell; | ||
| } | ||
| if other.timeout.is_some() { | ||
| self.timeout = other.timeout; | ||
| } | ||
| if other.confirm.is_some() { | ||
| self.confirm = other.confirm; | ||
| } | ||
| for (k, v) in other.tools { | ||
| self.tools.insert(k, v); | ||
| } | ||
| if !other.usage.is_empty() { | ||
| self.usage = other.usage; | ||
| } | ||
| } |
There was a problem hiding this comment.
The merge_toml_overlay method is missing several metadata fields that should likely be included in the overlay logic to ensure a complete merge of TOML-defined task metadata onto file tasks. Specifically, the following fields are currently omitted:
outputs: Users might want to define or override task caching behavior (e.g.,outputs = ["target/bin"]) inmise.tomlfor an auto-discovered script.extends: A file task might want to extend a template defined in the config hierarchy.- Sandbox fields (
deny_all,deny_read,deny_write,deny_net,deny_env,allow_read,allow_write,allow_net,allow_env): These are critical for security and should be configurable via the TOML overlay.
Consider adding these fields to the merge logic.
Greptile SummaryThis PR fixes two task-related issues from #8673: The implementation introduces Confidence Score: 5/5Safe to merge — all previous P1 concerns (depends_raw, outputs) were resolved in follow-up commits, and no new correctness issues were found. All findings are P2 or lower. The overlay merge logic is well-implemented with proper raw-snapshot seeding, path-source preservation for env directives, and a cache guard that prevents cross-contamination. E2E coverage is thorough including the usage-template edge case. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Config Loading] --> B[load_config_tasks\nTOML blocks]
A --> C[load_file_tasks\nauto-discovered scripts]
B --> D[merge_file_and_config_tasks]
C --> D
D --> E{Same name?}
E -- Yes --> F[file task stays as base\nTask::merge_toml_overlay\nenv/vars to overlay_env/overlay_vars\ndescription, dir, aliases, depends,\noutputs, sandbox, etc.]
E -- No --> G[keep as-is]
F --> H[Merged Task]
G --> H
H --> I{JSON output?}
I -- Yes --> J[task.dir config .await?\nresolved dir incl. inherited\ntask_config.dir]
I -- No --> K[raw task.dir field]
J --> L[JSON with resolved dir\n+ overlay_env in env array]
K --> M[Human-readable output]
Reviews (3): Last reviewed commit: "test(task): update test_task_info snapsh..." | Re-trigger Greptile |
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.15 x -- echo |
24.1 ± 0.6 | 23.4 | 30.4 | 1.00 |
mise x -- echo |
24.4 ± 2.0 | 23.5 | 45.5 | 1.01 ± 0.08 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.15 env |
23.5 ± 0.7 | 22.8 | 31.9 | 1.01 ± 0.04 |
mise env |
23.4 ± 0.4 | 22.7 | 25.9 | 1.00 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.15 hook-env |
24.2 ± 0.3 | 23.6 | 25.8 | 1.00 ± 0.02 |
mise hook-env |
24.1 ± 0.4 | 23.5 | 25.8 | 1.00 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.15 ls |
21.3 ± 0.4 | 20.5 | 22.8 | 1.00 |
mise ls |
21.3 ± 0.4 | 20.5 | 22.8 | 1.00 ± 0.02 |
xtasks/test/perf
| Command | mise-2026.4.15 | mise | Variance |
|---|---|---|---|
| install (cached) | 156ms | 155ms | +0% |
| ls (cached) | 81ms | 81ms | +0% |
| bin-paths (cached) | 86ms | 85ms | +1% |
| task-ls (cached) | 807ms | 789ms | +2% |
…dbox Address review feedback on the task TOML+file merge: - Store overlay env/vars in new `overlay_env` / `overlay_vars` fields that pair each directive with the overlay's own config path, so path-based directives like `_.file = ".env"` in a TOML block keep resolving relative to the TOML file rather than the file task's script path. - Keep `depends_raw`, `depends_post_raw`, `wait_for_raw` in sync with the merged deps so `render_depends_with_usage` does not drop overlay deps when re-rendering with usage args. - Merge `outputs`, `raw_outputs`, and the sandbox fields (`deny_*`, `allow_*`) from the TOML overlay. - `tasks ls --json` / `tasks info` now display overlay env alongside the task's own env. - Update `test_task_ls` snapshot to reflect the resolved dir. Extends the e2e test with a `_.file` assertion for overlay-source resolution.
Extend the `*_raw` (pre-render) snapshots with the overlay's *raw* dep
templates, not the already-rendered live deps, so `{{usage.*}}` refs in
overlay deps still resolve when `render_depends_with_usage` re-renders
with CLI-supplied arg values.
If a task's `depends_raw` is absent (defensive — current call sites all
render before merging), seed it from the pre-overlay live deps instead
of silently dropping the overlay's contribution.
Adds an e2e assertion for the usage-template case.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8702080. Configure here.
The env-resolution cache in `task_context_builder` is keyed on config path and only populated when the task has no task-specific state. The guards at both the read and write sites now also require `overlay_env` to be empty, so a file task with a TOML overlay does not share (or pollute) a cached entry with a sibling task that has no overlay.
### 🚀 Features - **(registry)** add .perl-version support for perl by @ergofriend in [#9102](#9102) - **(task)** add Tera template support for inline table run tasks by @iamkroot in [#9079](#9079) ### 🐛 Bug Fixes - **(env)** use runtime symlink paths for fuzzy versions by @jdx in [#9143](#9143) - **(github)** use full token resolution chain for attestation verification by @jdx in [#9154](#9154) - **(go)** Remove install-time version override for subpath packages by @c22 in [#9135](#9135) - **(npm)** respect install_before when resolving dist-tag versions by @webkaz in [#9145](#9145) - **(self-update)** ensure subcommand exists by @salim-b in [#9144](#9144) - **(task)** show available tasks when run target missing by @jdx in [#9141](#9141) - **(task)** forward task help args and add raw_args by @jdx in [#9118](#9118) - **(task)** remove red/yellow from task prefix colors by @lechuckcaptain in [#8782](#8782) - **(task)** merge TOML task block into same-named file task and surface resolved dir by @jdx in [#9147](#9147) - **(toolset)** round-trip serialized tool options by @atharvasingh7007 in [#9124](#9124) - **(vfox)** fallback to absolute bin path if env_keys not set by @80avin in [#9151](#9151) ### 📚 Documentation - make agent guide wording generic by @jdx in [#9142](#9142) ### 📦️ Dependency Updates - update ghcr.io/jdx/mise:deb docker digest to e019cb9 by @renovate[bot] in [#9160](#9160) - update ghcr.io/jdx/mise:copr docker digest to 8d25608 by @renovate[bot] in [#9159](#9159) - update ghcr.io/jdx/mise:rpm docker digest to 22e52da by @renovate[bot] in [#9161](#9161) - update ghcr.io/jdx/mise:alpine docker digest to a3da97c by @renovate[bot] in [#9158](#9158) - update rust docker digest to 4a2ef38 by @renovate[bot] in [#9162](#9162) - update ubuntu:24.04 docker digest to c4a8d55 by @renovate[bot] in [#9164](#9164) - update rust crate aws-lc-rs to v1.16.3 by @renovate[bot] in [#9165](#9165) - update ubuntu docker tag to resolute-20260413 by @renovate[bot] in [#9169](#9169) - update rust crate clap to v4.6.1 by @renovate[bot] in [#9166](#9166) - update taiki-e/install-action digest to a2352fc by @renovate[bot] in [#9163](#9163) - update rust crate ctor to 0.10 by @renovate[bot] in [#9170](#9170) - update rust crate tokio to v1.52.1 by @renovate[bot] in [#9167](#9167) - update rust crate rmcp-macros to 0.17 by @renovate[bot] in [#9173](#9173) - update rust crate signal-hook to 0.4 by @renovate[bot] in [#9177](#9177) - update rust crate zipsign-api to 0.2 by @renovate[bot] in [#9180](#9180) - update rust crate toml_edit to 0.25 by @renovate[bot] in [#9179](#9179) - update rust crate strum to 0.28 by @renovate[bot] in [#9178](#9178) ### 📦 Registry - add ibmcloud by @dnwe in [#9139](#9139) - add rush by @jdx in [#9146](#9146) ### New Contributors - @80avin made their first contribution in [#9151](#9151) - @atharvasingh7007 made their first contribution in [#9124](#9124) - @lechuckcaptain made their first contribution in [#8782](#8782) - @ergofriend made their first contribution in [#9102](#9102) - @dnwe made their first contribution in [#9139](#9139) ## 📦 Aqua Registry Updates #### New Packages (3) - [`controlplaneio-fluxcd/flux-operator`](https://github.com/controlplaneio-fluxcd/flux-operator) - [`dependency-check/DependencyCheck`](https://github.com/dependency-check/DependencyCheck) - [`kiro.dev/kiro-cli`](https://github.com/kiro.dev/kiro-cli) #### Updated Packages (2) - [`jreleaser/jreleaser/standalone`](https://github.com/jreleaser/jreleaser/standalone) - [`sigstore/cosign`](https://github.com/sigstore/cosign)

Summary
Fixes two related issues raised in #8673:
TOML
[tasks.<name>]block silently discarded when a file task with the same name exists. A user writing[tasks.my-script] env = { API_TOKEN = "secret" }inmise.tomlalongside an auto-discovered.mise/tasks/my-scriptwould get<unset>at runtime — the file task completely won and the TOML block's metadata was lost. Now the TOML block is treated as a metadata overlay: env, description, dir, aliases, depends, tools, etc. are merged into the file task whilerun/file/config_sourcestay with the file task base.mise tasks ls --jsonandmise tasks info --jsonreporteddir: nullfor tasks that inheritedtask_config.direven when runtime correctly used the inherited dir. The JSON output now reports the resolved dir so consumers see where the task will actually run.Changes
src/task/mod.rs: newTask::merge_toml_overlaythat overlays a TOML-block's non-default fields onto a file task.src/config/mod.rs: newmerge_file_and_config_taskshelper used by bothload_tasks_in_dir(local) andload_config_and_file_tasks(global) so the merge applies consistently in both paths.src/cli/tasks/ls.rs,src/cli/tasks/info.rs: resolvetask.dir(config)for thedirfield in JSON output.Test plan
e2e/tasks/test_task_file_toml_merge— file task + TOML overlay produces merged env/descriptione2e/tasks/test_task_ls_json_resolved_dir— JSON reports inherited dir instead of nulle2e/tasks/test_task_config_dir_inheritancestill passesmise run test:e2e tasks(all 146 task tests) passmise run test:unitpasses (649 tests)mise run lint-fixcleanNote
Medium Risk
Changes task-loading precedence and env/dep resolution semantics for same-named TOML+file tasks, which could affect existing projects that relied on the previous override behavior; JSON output shape also changes (
dirbecomes non-null in more cases).Overview
Fixes task precedence so a TOML
[tasks.<name>]block is no longer dropped when an auto-discovered executable task with the same name exists; instead the TOML block overlays metadata (env/vars with correct relative-path resolution, description, aliases, deps including{{usage.*}}templates, dir, flags, tools, sandbox settings) onto the file task.Updates
mise tasks ls --jsonandmise tasks info --jsonto emit the resolved task directory (including inheritedtask_config.dir) and to include overlay env entries in displayed env lists. Adds/updates e2e coverage for TOML+file task merging and for resolveddirappearing in JSON outputs.Reviewed by Cursor Bugbot for commit aa842f6. Bugbot is set up for automated code reviews on this repo. Configure here.