fix(tool-stub): detect binary names from single-file downloads#6281
fix(tool-stub): detect binary names from single-file downloads#6281
Conversation
When generating tool stubs for single binary files (not archives), the binary name should be extracted from the URL by removing platform/arch suffixes. This allows the bin field to be correctly set when it differs from the stub filename. For example, a stub named "jq-test" downloading "jq-macos-arm64" will now correctly set bin="jq" since the actual binary is "jq", not "jq-macos-arm64". 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull Request Overview
This PR fixes binary name detection for single-file downloads in tool-stub generation, ensuring that the bin field is correctly set to the actual binary command name rather than the filename with platform/architecture suffixes.
- Added helper function to extract tool names from URLs by removing platform/arch suffixes
- Updated tool-stub generation logic to detect binary names even when
--skip-downloadis used - Enhanced binary name detection for single binary files vs archives
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/cli/generate/tool_stub.rs | Added extract_tool_name_from_filename() function and updated logic to detect binary names from URLs when skipping downloads |
| e2e/cli/test_generate_tool_stub | Added comprehensive e2e test covering single binary files, archives, explicit bin parameters, and download scenarios |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
src/cli/generate/tool_stub.rs
Outdated
| let suffixes = [ | ||
| "-linux-x64", | ||
| "-linux-x86_64", | ||
| "-linux-amd64", | ||
| "-linux-arm64", | ||
| "-linux-aarch64", | ||
| "-darwin-x64", | ||
| "-darwin-x86_64", | ||
| "-darwin-amd64", | ||
| "-darwin-arm64", | ||
| "-darwin-aarch64", | ||
| "-macos-x64", | ||
| "-macos-x86_64", | ||
| "-macos-amd64", | ||
| "-macos-arm64", | ||
| "-macos-aarch64", | ||
| "-windows-x64", | ||
| "-windows-x86_64", | ||
| "-windows-amd64", | ||
| "-windows-arm64", | ||
| "-windows-aarch64", | ||
| "-win-x64", | ||
| "-win-x86_64", | ||
| "-win-amd64", | ||
| "-win-arm64", | ||
| "-win-aarch64", | ||
| "-x86_64", | ||
| "-x64", | ||
| "-amd64", | ||
| "-arm64", | ||
| "-aarch64", | ||
| "-linux", | ||
| "-darwin", | ||
| "-macos", | ||
| "-windows", | ||
| "-win", | ||
| ]; | ||
|
|
||
| let mut result = name.to_string(); | ||
| for suffix in &suffixes { |
There was a problem hiding this comment.
[nitpick] The hardcoded suffix array could be difficult to maintain as new platform/architecture combinations emerge. Consider extracting this to a constant or configuration file to improve maintainability and make it easier to add new suffixes in the future.
| let suffixes = [ | |
| "-linux-x64", | |
| "-linux-x86_64", | |
| "-linux-amd64", | |
| "-linux-arm64", | |
| "-linux-aarch64", | |
| "-darwin-x64", | |
| "-darwin-x86_64", | |
| "-darwin-amd64", | |
| "-darwin-arm64", | |
| "-darwin-aarch64", | |
| "-macos-x64", | |
| "-macos-x86_64", | |
| "-macos-amd64", | |
| "-macos-arm64", | |
| "-macos-aarch64", | |
| "-windows-x64", | |
| "-windows-x86_64", | |
| "-windows-amd64", | |
| "-windows-arm64", | |
| "-windows-aarch64", | |
| "-win-x64", | |
| "-win-x86_64", | |
| "-win-amd64", | |
| "-win-arm64", | |
| "-win-aarch64", | |
| "-x86_64", | |
| "-x64", | |
| "-amd64", | |
| "-arm64", | |
| "-aarch64", | |
| "-linux", | |
| "-darwin", | |
| "-macos", | |
| "-windows", | |
| "-win", | |
| ]; | |
| let mut result = name.to_string(); | |
| for suffix in &suffixes { | |
| let suffixes = &TOOL_NAME_SUFFIXES; | |
| let mut result = name.to_string(); | |
| for suffix in suffixes { |
src/cli/generate/tool_stub.rs
Outdated
| for suffix in &suffixes { | ||
| if let Some(pos) = result.rfind(suffix) { | ||
| result.truncate(pos); |
There was a problem hiding this comment.
The current logic only removes the first matching suffix found, but this could lead to incorrect results. For example, a filename like 'tool-linux-x64-linux' would only remove '-linux-x64' and leave '-linux' at the end. Consider iterating until no more suffixes can be removed or using a more sophisticated pattern matching approach.
| for suffix in &suffixes { | |
| if let Some(pos) = result.rfind(suffix) { | |
| result.truncate(pos); | |
| loop { | |
| let mut found = false; | |
| for suffix in &suffixes { | |
| if result.ends_with(suffix) { | |
| let len = result.len() - suffix.len(); | |
| result.truncate(len); | |
| found = true; | |
| break; | |
| } | |
| } | |
| if !found { |
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.9 x -- echo |
19.1 ± 0.3 | 18.7 | 21.6 | 1.00 |
mise x -- echo |
19.2 ± 0.4 | 18.6 | 23.4 | 1.00 ± 0.03 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.9 env |
18.6 ± 0.3 | 18.2 | 21.8 | 1.00 |
mise env |
18.9 ± 0.7 | 18.1 | 26.6 | 1.02 ± 0.04 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.9 hook-env |
18.7 ± 0.5 | 17.8 | 22.2 | 1.02 ± 0.04 |
mise hook-env |
18.3 ± 0.4 | 17.8 | 22.7 | 1.00 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.9 ls |
16.2 ± 0.3 | 15.7 | 17.7 | 1.00 |
mise ls |
17.8 ± 1.6 | 15.9 | 45.6 | 1.10 ± 0.10 |
xtasks/test/perf
| Command | mise-2025.9.9 | mise | Variance |
|---|---|---|---|
| install (cached) | 173ms | ✅ 111ms | +55% |
| ls (cached) | 63ms | 63ms | +0% |
| bin-paths (cached) | 69ms | 70ms | -1% |
| task-ls (cached) | 482ms | 481ms | +0% |
✅ Performance improvement: install cached is 55%
Simplified the test to just generate a git-branchless tool stub and attempt to execute it, which directly reproduces the original issue where the bin field was empty.
When strip_components removes all path components (e.g., stripping 'git-branchless' results in empty string), don't set an empty bin field. Instead, keep the original binary name. This fixes the issue where tool stubs had bin="" which prevented the HTTP backend from using its auto-detection logic. Also updated CLAUDE.md with correct debug environment variables.
### 📦 Registry - use asdf to install semver-tool by @jylenhof in [#6233](#6233) ### 🐛 Bug Fixes - **(tool-stub)** detect binary names from single-file downloads by @jdx in [#6281](#6281) ### 🚜 Refactor - allow any collection types in deserialize_arr by @risu729 in [#6282](#6282) - use deserialize_arr for task runs by @risu729 in [#6253](#6253) ### 📚 Documentation - **(getting-started)** add backends step with diagram, github example, env vars and simple tasks by @jdx in [#6288](#6288) - simplify OS detection in tool plugin development by @jdx in [#6287](#6287) - update backend plugin template references by @jdx in [942f5eb](942f5eb) ### 📦️ Dependency Updates - update rust crate chrono to v0.4.42 by @renovate[bot] in [#6274](#6274) - update taiki-e/install-action digest to 0154864 by @renovate[bot] in [#6273](#6273) ### Chore - **(schema)** fix schema for subtasks by @risu729 in [#6289](#6289) - update render:schema task by @risu729 in [#6275](#6275) Co-authored-by: mise-en-dev <release@mise.jdx.dev>
## [2025.9.12](https://github.com/jdx/mise/compare/v2025.9.11..v2025.9.12) - 2025-09-16 ### 🐛 Bug Fixes - **(ci)** properly exclude aqua-registry files from hk linting by @jdx in [42d7758](jdx/mise@42d7758) ### Chore - **(release)** embed aqua-registry under crate and publish like vfox by @jdx in [#6306](jdx/mise#6306) - ignore aqua-registry assets from prettier by @jdx in [047d77b](jdx/mise@047d77b) - disable "useless cat" shellcheck by @jdx in [a6def59](jdx/mise@a6def59) ## [2025.9.11](https://github.com/jdx/mise/compare/v2025.9.10..v2025.9.11) - 2025-09-16 ### 📦 Registry - indicate aws-cli is v2 by @jayvdb in [#6300](jdx/mise#6300) ### 🚀 Features - **(ci)** run all registry tools when workflow_dispatch is triggered by @jdx in [#6295](jdx/mise#6295) - **(cli)** handle non-existent working directories gracefully by @jdx in [#6304](jdx/mise#6304) - **(set)** add -E/--env flag to mise set command by @jdx in [#6291](jdx/mise#6291) - **(tasks)** make auto outputs default by @risu729 in [#6137](jdx/mise#6137) ### 🐛 Bug Fixes - align crate versions to fix release-plz script by @jdx in [5a464e9](jdx/mise@5a464e9) ### 🚜 Refactor - **(aqua)** extract aqua registry into internal subcrate by @jdx in [#6293](jdx/mise#6293) ### 📚 Documentation - fix mkdir paths by @risu729 in [#6298](jdx/mise#6298) - fix rust profile default by @risu729 in [#6305](jdx/mise#6305) ### Chore - **(aqua-registry)** remove unused aqua-registry files by @jdx in [#6294](jdx/mise#6294) - **(release)** make release-plz idempotent for existing crate versions by @jdx in [dbdfd6a](jdx/mise@dbdfd6a) - **(release)** skip publishing mise when aqua-registry is a path dep by @jdx in [47efffd](jdx/mise@47efffd) - keep aqua-registry LICENSE file by @risu729 in [#6297](jdx/mise#6297) ### New Contributors - @jayvdb made their first contribution in [#6300](jdx/mise#6300) ## [2025.9.10](https://github.com/jdx/mise/compare/v2025.9.9..v2025.9.10) - 2025-09-13 ### 📦 Registry - use asdf to install semver-tool by @jylenhof in [#6233](jdx/mise#6233) ### 🐛 Bug Fixes - **(tool-stub)** detect binary names from single-file downloads by @jdx in [#6281](jdx/mise#6281) ### 🚜 Refactor - allow any collection types in deserialize_arr by @risu729 in [#6282](jdx/mise#6282) - use deserialize_arr for task runs by @risu729 in [#6253](jdx/mise#6253) ### 📚 Documentation - **(getting-started)** add backends step with diagram, github example, env vars and simple tasks by @jdx in [#6288](jdx/mise#6288) - simplify OS detection in tool plugin development by @jdx in [#6287](jdx/mise#6287) - update backend plugin template references by @jdx in [942f5eb](jdx/mise@942f5eb) ### 📦️ Dependency Updates - update rust crate chrono to v0.4.42 by @renovate[bot] in [#6274](jdx/mise#6274) - update taiki-e/install-action digest to 0154864 by @renovate[bot] in [#6273](jdx/mise#6273) ### Chore - **(schema)** fix schema for subtasks by @risu729 in [#6289](jdx/mise#6289) - update render:schema task by @risu729 in [#6275](jdx/mise#6275) ## [2025.9.9](https://github.com/jdx/mise/compare/v2025.9.8..v2025.9.9) - 2025-09-11 ### 🐛 Bug Fixes - **(backend)** make HTTP installs atomic and serialize with cache lock by @jdx in [#6259](jdx/mise#6259) - **(env)** allow nested env._.path directives by @risu729 in [#6160](jdx/mise#6160) - **(env)** disallow nested env objects by @risu729 in [#6268](jdx/mise#6268) - **(schema)** allow nested arrays in task.depends by @risu729 in [#6265](jdx/mise#6265) - **(task)** resolve jobs=1 hang and keep-order panic; improve Ctrl-C handling by @jdx in [#6264](jdx/mise#6264) - **(tasks)** stop CLI group after first failure without --continue-on-error by @jdx in [#6270](jdx/mise#6270) ### 📚 Documentation - fixed toml issues in URL replacements settings documentation by @ThomasSteinbach in [#6269](jdx/mise#6269) ### Chore - **(schema)** strict oneOf branches and DRY env_directive in schemas by @jdx in [#6271](jdx/mise#6271) - add schema linter by @risu729 in [#6267](jdx/mise#6267) ## [2025.9.8](https://github.com/jdx/mise/compare/v2025.9.7..v2025.9.8) - 2025-09-10 ### 🐛 Bug Fixes - **(tasks)** prevent hang when task fails in sequence by @jdx in [#6260](jdx/mise#6260) - **(version)** fetch mise version if cached version is older than the current by @risu729 in [#6252](jdx/mise#6252) ### 📦️ Dependency Updates - update rhysd/action-setup-vim action to v1.4.3 by @renovate[bot] in [#6249](jdx/mise#6249) ## [2025.9.7](https://github.com/jdx/mise/compare/v2025.9.6..v2025.9.7) - 2025-09-09 ### 🐛 Bug Fixes - **(env)** allow mixed map for env._.file by @risu729 in [#6148](jdx/mise#6148) - **(tasks)** restore parallel starts with poetry via list_paths cache and stable exec-env cache by @jdx in [#6237](jdx/mise#6237) - add 'unknown' to the list of OS patterns by @efussi in [#6235](jdx/mise#6235) - propagate errors from backend installs by @jdx in [#6236](jdx/mise#6236) ### 📦️ Dependency Updates - update taiki-e/install-action digest to 0c5db7f by @renovate[bot] in [#6244](jdx/mise#6244) - update golang docker tag to v1.25.1 by @renovate[bot] in [#6247](jdx/mise#6247) - update dependency vitepress to v1.6.4 by @renovate[bot] in [#6246](jdx/mise#6246) ### New Contributors - @efussi made their first contribution in [#6235](jdx/mise#6235)
Summary
--skip-downloadis usedProblem
When generating tool stubs for single binary files (not archives), the
binfield was being left empty or set incorrectly. For example, a stub named "jq-test" downloading "jq-macos-arm64" should setbin="jq"since the actual binary command is "jq", not "jq-macos-arm64".Solution
extract_tool_name_from_filename()function that removes common platform/architecture suffixes from filenamesTest Plan
test_generate_tool_stubthat verifies:🤖 Generated with Claude Code