fix(install): render tera templates in tool postinstall hooks#8978
Conversation
Tool-level postinstall scripts (e.g. `[tools.ripgrep] postinstall`)
were passing the script directly to the shell without tera template
rendering, causing template variables like `{{tools.ripgrep.path}}`
to fail silently as invalid shell syntax.
Fixes #8366
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR adds Tera template rendering to tool-level Confidence Score: 5/5Safe to merge — the fix is correct for the primary use case and consistent with the existing hooks.rs pattern. The single changed file applies a well-established pattern (get_tera + tera_ctx + render_str) that already exists for project-level hooks. The newly installed tool is guaranteed to appear in the tera context because its incomplete-file is removed before run_postinstall_hook is called. The one noted concern (OnceCell cache race under parallel installs) is an edge case that predates this PR and does not affect the stated fix; all remaining findings are P2. No files require special attention.
|
| Filename | Overview |
|---|---|
| src/backend/mod.rs | Adds Tera context rendering before script execution in run_postinstall_hook; implementation is consistent with the hooks.rs pattern and correctly resolves the newly-installed tool because the incomplete-file is removed prior to this call. |
Sequence Diagram
sequenceDiagram
participant Install as install_version_
participant PostHook as run_postinstall_hook
participant TS as Toolset.tera_ctx (OnceCell)
participant Tera as get_tera / render_str
participant Shell as CmdLineRunner
Install->>Install: remove incomplete_file_path
Install->>PostHook: run_postinstall_hook(ctx, tv, script)
PostHook->>PostHook: exec_env() → env_vars
PostHook->>PostHook: list_bin_paths() → path_env
PostHook->>TS: tera_ctx(&ctx.config)
TS-->>PostHook: context with tools map (tool now installed)
PostHook->>Tera: get_tera(source_dir)
Tera-->>PostHook: tera instance
PostHook->>Tera: render_str(script, tera_ctx)
Tera-->>PostHook: rendered_script
PostHook->>Shell: execute rendered_script
Reviews (1): Last reviewed commit: "fix(install): render tera templates in t..." | Re-trigger Greptile
| } | ||
|
|
||
| // Render tera template variables (e.g. {{tools.ripgrep.path}}) | ||
| let tera_ctx = ctx.ts.tera_ctx(&ctx.config).await?; |
There was a problem hiding this comment.
Cached
tera_ctx may omit concurrently-installing tools
tera_ctx is stored in a OnceCell on the shared Arc<Toolset>. When jobs > 1, tools are installed concurrently. The first tool to reach this line will compute and permanently cache the context; subsequent tools' postinstall hooks will reuse that snapshot. If Tool B's postinstall runs {{tools.B.path}} but Tool A's earlier postinstall already cached the context before B's incomplete-file was removed, B will be absent from tools and the template will silently produce an empty string or an error.
This only affects the (currently uncommon) case of multiple tools with postinstall hooks installed in the same parallel batch. For a single tool — the primary use-case here — the tool is guaranteed to be in the context because its incomplete-file is removed before this call.
There was a problem hiding this comment.
Code Review
This pull request integrates tera templating into the backend's script execution, enabling dynamic rendering of variables within scripts before they are run. A review comment highlights a potential edge case where the directory provided to get_tera might be None for non-path-based tools, suggesting that the get_tera function and the rendering context should be robustly designed to handle such scenarios.
| } | ||
|
|
||
| // Render tera template variables (e.g. {{tools.ripgrep.path}}) | ||
| let tera_ctx = ctx.ts.tera_ctx(&ctx.config).await?; | ||
| let dir = tv.request.source().path().and_then(|p| p.parent()); | ||
| let mut tera = get_tera(dir); | ||
| let rendered_script = tera.render_str(script, tera_ctx)?; |
There was a problem hiding this comment.
The get_tera function is called with dir which is derived from tv.request.source().path().and_then(|p| p.parent()). If tv.request.source().path() is None (e.g., for non-path based tools), dir will be None. This is acceptable, but it is better to ensure that get_tera handles None correctly and that the rendering context is robust against missing file system context.
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.7 x -- echo |
24.4 ± 0.3 | 23.7 | 27.7 | 1.00 |
mise x -- echo |
25.1 ± 0.5 | 24.3 | 29.7 | 1.03 ± 0.02 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.7 env |
23.6 ± 0.7 | 22.7 | 30.2 | 1.00 |
mise env |
23.9 ± 0.6 | 23.2 | 31.2 | 1.01 ± 0.04 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.7 hook-env |
24.9 ± 1.1 | 23.6 | 35.8 | 1.00 |
mise hook-env |
25.4 ± 0.7 | 24.1 | 27.7 | 1.02 ± 0.05 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.7 ls |
21.6 ± 0.6 | 20.7 | 24.2 | 1.00 |
mise ls |
22.0 ± 0.8 | 21.3 | 30.5 | 1.01 ± 0.05 |
xtasks/test/perf
| Command | mise-2026.4.7 | mise | Variance |
|---|---|---|---|
| install (cached) | 155ms | 155ms | +0% |
| ls (cached) | 81ms | 81ms | +0% |
| bin-paths (cached) | 85ms | 85ms | +0% |
| task-ls (cached) | 815ms | 797ms | +2% |
### 🚀 Features - **(config)** add lockfile_platforms setting to restrict lockfile platforms by @cameronbrill in [#8966](#8966) - **(sandbox)** support wildcard patterns in allow_env by @jdx in [#8974](#8974) - bump usage-lib v2 → v3 to render examples in task --help by @baby-joel in [#8890](#8890) ### 🐛 Bug Fixes - **(activate)** handle empty __MISE_FLAGS array with set -u on bash 3.2 by @jdx in [#8988](#8988) - **(env)** add trace logging for module hook PATH diagnostics by @jdx in [#8981](#8981) - **(go)** Query module proxy directly for version resolution by @c22 in [#8968](#8968) - **(install)** render tera templates in tool postinstall hooks by @jdx in [#8978](#8978) - **(install)** add missing env vars to tool postinstall hooks by @jdx in [#8977](#8977) - **(task)** prevent hang when skipped task has dependents by @jdx in [#8937](#8937) - **(task)** invalidate dependent task sources when dependency runs by @jdx in [#8975](#8975) - **(task)** prevent deadlock when MISE_JOBS=1 with sub-task references by @jdx in [#8976](#8976) - **(task)** fetch remote task files before parsing usage specs by @jdx in [#8979](#8979) - **(task)** prevent panic when running parallel sub-tasks with replacing output by @jdx in [#8986](#8986) - **(upgrade)** update lockfile and config when upgrading to specific version by @jdx in [#8983](#8983) ### 📚 Documentation - **(node)** remove "recommended for teams" from pin example by @jdx in [b334363](b334363) ### 📦️ Dependency Updates - update ghcr.io/jdx/mise:alpine docker digest to 17a29f2 by @renovate[bot] in [#8995](#8995) - update docker/dockerfile:1 docker digest to 2780b5c by @renovate[bot] in [#8994](#8994) ### New Contributors - @baby-joel made their first contribution in [#8890](#8890) - @cameronbrill made their first contribution in [#8966](#8966) - @c22 made their first contribution in [#8968](#8968)
Summary
[tools.ripgrep] postinstall) were passing the script directly to the shell without tera template rendering{{tools.ripgrep.path}}would fail silently as invalid shell syntax[hooks] postinstallFixes #8366
Test plan
postinstall = "echo {{tools.ripgrep.path}}"and verify the path is rendered🤖 Generated with Claude Code
Note
Low Risk
Low risk: this only changes how tool-level
postinstallscripts are prepared (template rendering) before being passed to the shell, without altering installation/download logic.Overview
Tool-level
postinstallhooks are now rendered through Tera before execution, enabling template variables like{{tools.ripgrep.path}}to work in tool configuration.run_postinstall_hooknow builds a Tera context from the resolved toolset, renders the script withget_tera(scoped to the tool source directory), and executes the rendered result instead of the raw script.Reviewed by Cursor Bugbot for commit 84fd7b8. Bugbot is set up for automated code reviews on this repo. Configure here.