Skip to content

fix(config): let tool versions contain a colon - #11580

Merged
jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix/tool-version-template-colon
Aug 2, 2026
Merged

fix(config): let tool versions contain a colon#11580
jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix/tool-version-template-colon

Conversation

@JamBalaya56562

@JamBalaya56562 JamBalaya56562 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

A : anywhere in a [tools] version makes config loading fail. The version's selector is parsed at TOML-deserialize time, before templates are rendered, so a template containing a colon gets split mid-expression:

[tools."npm:cowsay"]
version = '''{{ exec(command='echo VER: 1.2.3') | split(pat=': ') | last | trim }}'''
  × Invalid TOML in config file
   ╭─[mise.toml:1:1]
 1 │ [tools."npm:cowsay"]
   · ──────────┬─────────
   ·           ╰── invalid prefix: {{ exec(command='echo VER

Not template-specific — "npm:cowsay" = "1.2.3:x" fails the same way with invalid tool: invalid prefix: 1.2.3. Not a regression either: colon-free templates work on every release back to v2025.7.0, so it has always been the colon.

Reported in #5531, where the reporter's template contained 'ANSIBLE_VERSION: '. Their diagnosis was right.

Fix

Keep the version request as the raw string until it is rendered.

mise.toml's [tools] was the one place that parsed first and rendered second:

order
[tasks.*.tools] store string → Task::render → parse
.tool-versions render whole body → parse
mise.toml [tools] parse → Display → render → re-parse

MiseTomlTool now holds request: String, and ToolRequest::new — which already re-parses the rendered string — is where selectors are resolved. prefix:, ref:, path:, sub-N: and system all still work, including when they come out of a template (prefix:{{ env.X }}).

ToolVersionType is deliberately untouched. Guarding it on contains_template_syntax would have been a smaller diff, but it is also the version filter for remote listings in backend/mod.rs and backend/github.rs (Ok(ToolVersionType::Version(_)) => true), and relaxing it there would let an upstream tag containing template syntax through. Removing the caller is the narrower change even though the diff is larger.

Collapsing From<ToolRequest> for MiseTomlTool onto ToolRequest::version() also drops a latent bug: the Ref arm passed (ref_, ref_type) where both FromStr and Display take the type first, so branch:main round-tripped to main:branch.

Diagnostics

Deserialization no longer rejects a bad version, so the failure now surfaces in to_tool_request_set() — further from the config, and behind five call sites that .ok() the result (install.rs, upgrade.rs ×2, lock.rs ×2, task_tool_installer.rs). To keep it actionable the error names the file, the template, and what it became:

0: invalid version for terraform in ~/mise.toml: {{ exec(command='echo bogus:1.0') | trim }} rendered to "bogus:1.0"
1: invalid tool version request: bogus:1.0

Making those callers stop swallowing the error is a separate change; I left it alone here.

Tests

  • test_colon_in_templated_tool_version — the reported case, plus ref = "{{ env.BRANCH }}" and a {% if %} block. Asserts the resulting ToolRequest, so it pins that the selector survives being resolved late rather than pinning the intermediate representation.
  • test_templated_tool_version_rendering_to_bad_selector — the deferred error still names file, template and rendered value.
  • e2e/config/test_tool_version_vars — the repro, prefix:{{ env.X }}, and a negative case. Every template in that file was colon-free before this.
  • e2e/config/test_trust_safe_config — a colon-bearing template in [tools] must still require trust and must not execute. Previously the version parser rejected it first, so this pins that trust is what stops it now.

Verified on Windows against the released v2026.7.18 binary: the repro flips, and prefix: / path: / ref: / sub-1: / system / plain / colon-free templates / both table forms are byte-identical, as is mise use write-back (an unrelated tool's prefix: selector survives).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Tool version templates containing colons are now supported in configuration files.
    • Selectors are preserved and applied correctly after template rendering.
  • Bug Fixes

    • Invalid rendered tool versions now produce clearer errors with relevant configuration context.
    • Unsafe templated version values are blocked before execution.

A `:` anywhere in a `[tools]` version made config loading fail, because the
version's *selector* was parsed at TOML-deserialize time, before templates were
rendered. A template containing one -- the reported case was
`'ANSIBLE_VERSION: '` inside an `exec()` -- was split mid-expression:

    [tools."npm:cowsay"]
    version = '''{{ exec(command='echo VER: 1.2.3') | split(pat=': ') | last }}'''

      x Invalid TOML in config file
     1 | [tools."npm:cowsay"]
       |           ╰── invalid prefix: {{ exec(command='echo VER

Not template-specific: `"npm:cowsay" = "1.2.3:x"` failed the same way. Not a
regression either -- colon-free templates work back to v2025.7.0.

Keep the version request as the raw string until it is rendered, which is what
`[tasks.*.tools]` and `.tool-versions` already do; `mise.toml`'s `[tools]` was
the one place that parsed first and rendered second. `ToolRequest::new` parses
the rendered string, so `prefix:`/`ref:`/`path:`/`sub-N:` are still honoured.

`ToolVersionType` is deliberately untouched: it is also the version filter for
remote listings in `backend/mod.rs` and `backend/github.rs`, and relaxing it
there would let a tag containing template syntax through.

Deserialization no longer rejects a bad version, so the failure surfaces later,
in `to_tool_request_set`, which several callers `.ok()` away. Name the file, the
template, and what it rendered to, so the error is still actionable:

    invalid version for terraform in ~/mise.toml:
      {{ exec(command='echo bogus:1.0') }} rendered to "bogus:1.0"

Collapsing `From<ToolRequest> for MiseTomlTool` onto `ToolRequest::version()`
also drops a latent bug: the `Ref` arm passed `(ref_, ref_type)` where both
`FromStr` and `Display` take the type first, so `branch:main` round-tripped to
`main:branch`.
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Tool version requests now remain unparsed until after template rendering. This allows colon-containing templates, preserves rendered selectors, and reports invalid rendered values with configuration context. Tests cover successful resolution, invalid selectors, and trust enforcement.

Changes

Tool version template parsing

Layer / File(s) Summary
Store raw tool requests
src/config/config_file/mise_toml.rs
MiseTomlTool and string tool definitions now retain raw request strings.
Render and validate requests
src/config/config_file/mise_toml.rs
Rendered requests are parsed after template expansion. Errors include the config path, original template, and rendered value when applicable.
Preserve requests and verify behavior
src/config/config_file/mise_toml.rs, e2e/config/test_tool_version_vars, e2e/config/test_trust_safe_config
Tool reconstruction uses ToolRequest::version(). Tests cover colon-containing templates, rendered selectors, invalid selectors, and trust checks.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • jdx/mise#11069: Both changes modify tool-selector parsing in mise_toml.rs.
  • jdx/mise#11255: Both changes involve ToolRequest selector parsing and preservation.

Suggested reviewers: jdx, risu729

Poem

A rabbit found a colon in the hay,
So raw requests waited for their day.
Templates rendered, selectors grew clear,
Invalid values now leave context near.
Trust stayed firm beside the gate,
And tests hopped through every state.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: allowing colons in tool version strings.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@JamBalaya56562
JamBalaya56562 marked this pull request as ready for review August 2, 2026 00:50
@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR defers parsing [tools] version selectors until after template rendering so colons inside templates are preserved.

  • Stores tool requests as raw strings during TOML deserialization.
  • Parses rendered requests in to_tool_request_set() and adds contextual errors for invalid results.
  • Simplifies ToolRequest write-back while preserving selectors and options.
  • Adds unit and end-to-end coverage for colon-bearing templates, selectors, diagnostics, and trust enforcement.

Confidence Score: 5/5

The PR appears safe to merge with no actionable defects identified.

The changed parsing order preserves raw templates until rendering while retaining selector parsing afterward, and the added trust coverage confirms that newly accepted colon-bearing templates do not bypass the existing execution boundary.

Important Files Changed

Filename Overview
src/config/config_file/mise_toml.rs Defers tool-request parsing until after rendering, improves invalid-request diagnostics, and preserves canonical selector serialization.
e2e/config/test_tool_version_vars Covers colon-bearing templates, selectors produced by templates, and invalid rendered selectors.
e2e/config/test_trust_safe_config Confirms colon-bearing executable templates remain blocked in untrusted configuration.

Reviews (1): Last reviewed commit: "fix(config): let tool versions contain a..." | Re-trigger Greptile

@jdx
jdx merged commit a8f6643 into jdx:main Aug 2, 2026
29 checks passed
@JamBalaya56562
JamBalaya56562 deleted the fix/tool-version-template-colon branch August 2, 2026 01:41
This was referenced Aug 2, 2026
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