fix(env): parse --env=VALUE and -E=VALUE flag forms correctly#8889
Conversation
The environment() function only handled space-separated flag/value pairs (e.g., `--env production`) but silently ignored the equals-sign form (e.g., `--env=production`), causing it to fall through to the default environment from .miserc.toml. Fixes #8883 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the argument parsing logic in src/env.rs to support both space-separated (e.g., --env production) and equals-separated (e.g., --env=production) flags. A logic flaw was identified where space-separated values are not skipped, potentially leading to double-processing of arguments. A more efficient implementation using an iterator and split_once('=') was suggested to resolve this and simplify the code.
src/env.rs
Outdated
| let arg_prefixes: Vec<String> = arg_defs.iter().map(|a| format!("{a}=")).collect(); | ||
| // Try to get from command line args first | ||
| args.windows(2) | ||
| .take_while(|window| !window.iter().any(|a| a == "--")) | ||
| .filter_map(|window| { | ||
| if arg_defs.contains(&*window[0]) { | ||
| Some(window[1].clone()) | ||
| } else { | ||
| None | ||
| // Handles both `--env production` (separate args) and `--env=production` (joined with =) | ||
| let mut values = Vec::new(); | ||
| let args_before_dashdash: Vec<_> = args.iter().take_while(|a| a.as_str() != "--").collect(); | ||
| for (i, arg) in args_before_dashdash.iter().enumerate() { | ||
| if let Some(prefix) = arg_prefixes.iter().find(|p| arg.starts_with(p.as_str())) { | ||
| values.push(arg[prefix.len()..].to_string()); | ||
| } else if arg_defs.contains(arg.as_str()) { | ||
| if let Some(next) = args_before_dashdash.get(i + 1) { | ||
| values.push(next.to_string()); | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
The current implementation of the argument parsing loop has a logic flaw: it does not skip the next argument when it is consumed as a value for a space-separated flag (e.g., --env production). This causes the value to be processed again as a potential flag in the next iteration. For example, with mise --env --env=prod, the current code would add both --env=prod and prod to the values list.
Additionally, the logic can be simplified and made more efficient by using an iterator and split_once('='), which avoids the need to pre-allocate arg_prefixes and the intermediate args_before_dashdash vector.
| let arg_prefixes: Vec<String> = arg_defs.iter().map(|a| format!("{a}=")).collect(); | |
| // Try to get from command line args first | |
| args.windows(2) | |
| .take_while(|window| !window.iter().any(|a| a == "--")) | |
| .filter_map(|window| { | |
| if arg_defs.contains(&*window[0]) { | |
| Some(window[1].clone()) | |
| } else { | |
| None | |
| // Handles both `--env production` (separate args) and `--env=production` (joined with =) | |
| let mut values = Vec::new(); | |
| let args_before_dashdash: Vec<_> = args.iter().take_while(|a| a.as_str() != "--").collect(); | |
| for (i, arg) in args_before_dashdash.iter().enumerate() { | |
| if let Some(prefix) = arg_prefixes.iter().find(|p| arg.starts_with(p.as_str())) { | |
| values.push(arg[prefix.len()..].to_string()); | |
| } else if arg_defs.contains(arg.as_str()) { | |
| if let Some(next) = args_before_dashdash.get(i + 1) { | |
| values.push(next.to_string()); | |
| } | |
| }) | |
| } | |
| } | |
| let mut values = Vec::new(); | |
| let mut it = args.iter().take_while(|a| a.as_str() != "--"); | |
| while let Some(arg) = it.next() { | |
| if let Some((flag, value)) = arg.split_once('=') { | |
| if arg_defs.contains(flag) { | |
| values.push(value.to_string()); | |
| } | |
| } else if arg_defs.contains(arg.as_str()) { | |
| if let Some(next) = it.next() { | |
| values.push(next.to_string()); | |
| } | |
| } | |
| } | |
| values |
Greptile SummaryThis PR fixes a silent bug where The fix replaces the Key changes:
Confidence Score: 4/5Safe to merge after fixing the typo on line 53 of the e2e test — the core env.rs logic is correct and well-reasoned. The src/env.rs implementation is sound: it correctly handles all four flag forms, comma-separated values, and the mise run positional-arg boundary. One P1 defect exists in the new e2e test — line 53 uses e2e/cli/test_env_flag_forms line 53 — spurious extra Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["environment(args)"] --> B{IS_RUNNING_AS_SHIM?}
B -- Yes --> C[return vec! empty]
B -- No --> D[iter over args, stop at --]
D --> E{next arg?}
E -- None --> F[collect values]
E -- Some arg --> G{arg contains '='?}
G -- Yes --> H{flag part in arg_defs?}
H -- Yes --> I[push value part] --> E
H -- No --> E
G -- No --> J{arg in arg_defs?}
J -- Yes --> K[it.next as next_arg
push next_arg] --> E
J -- No --> L{arg starts with '-'?}
L -- Yes --> E
L -- No --> M{in_run_subcommand?}
M -- Yes --> N[BREAK]
M -- No --> O{arg in run_subcommands?}
O -- Yes --> P[in_run_subcommand = true] --> E
O -- No --> E
F --> Q[flat_map: split each value on comma]
Q --> R{from_args empty?}
R -- No --> S[return from_args]
R -- Yes --> T[check MISE_ENV / MISE_PROFILE / .miserc.toml]
Reviews (4): Last reviewed commit: "refactor(env): simplify flag parsing wit..." | Re-trigger Greptile |
…rated values When parsing `--env production`, the value arg was revisited on the next loop iteration and could be double-processed if it happened to match a flag name (e.g., `--env -E`). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When parsing raw args for MISE_ENV, the environment() function scanned all args before `--`. This meant `mise run show-env -E dev` incorrectly set the environment to "dev", even though -E after the task name is a task argument, not a global flag. Now stops scanning for env flags once a positional arg (the task name) is seen after `run`/`r`. Also adds comprehensive e2e tests for all -E flag forms (=, space-separated, commas, before/after run). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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 2cb67b9. Configure here.
| } | ||
| if run_subcommands.contains(arg.as_str()) { | ||
| in_run_subcommand = true; | ||
| } |
There was a problem hiding this comment.
False run-subcommand detection from other flags' values
Low Severity
The in_run_subcommand heuristic doesn't account for values of other global flags being misidentified as positional args. Since environment() only knows about --env/-E/--profile/-P, the values of other value-taking flags like --cd/-C fall through to the positional-arg branch. If that value happens to be "run" or "r" (e.g., mise -C run env -E dev), in_run_subcommand is falsely set to true, causing subsequent -E flags to be silently ignored. The old windows(2) code didn't have this issue since it had no in_run_subcommand guard. Having a directory named run is plausible in real projects.
Reviewed by Cursor Bugbot for commit 2cb67b9. Configure here.
Use an iterator with split_once('=') instead of index-based loop with
arg_prefixes and skip_next. The iterator naturally handles consumed
values via it.next(), and split_once cleanly handles the =VALUE form.
Also adds --profile=VALUE and -P=VALUE e2e test coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hyperfine Performance
|
| Command | mise-2026.4.3 | mise | Variance |
|---|---|---|---|
| install (cached) | 116ms | 116ms | +0% |
| ls (cached) | 66ms | 66ms | +0% |
| bin-paths (cached) | 68ms | 68ms | +0% |
| task-ls (cached) | 719ms | -72% |
### 🚀 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)


Summary
--env=productionand-E=production(equals-sign form) being silently ignored, causing fallback to the default environment from.miserc.tomlenvironment()function insrc/env.rsparsed args usingwindows(2)which only matched space-separated flag/value pairs (--env production), not the=-joined form--env productionand--env=production(and likewise-E,-P,--profile)Fixes #8883
Test plan
mise --env=production envproduces same output asmise --env production envmise -E=production envproduces same output asmise -E production envmise --env production envstill works as beforemise -E production envstill works as beforemise --env=dev,staging env🤖 Generated with Claude Code
Note
Medium Risk
Adjusts CLI argument parsing for environment selection, which can change which config/environment is applied at runtime; scope is localized and covered by new e2e tests.
Overview
Fixes environment/profile flag parsing so
--env=dev,-E=dev,--profile=dev, and-P=devare recognized (in addition to space-separated forms), including comma-separated multi-env values.Updates
environment()scanning to stop treating flags aftermise run <task>as global env selectors, and adds an e2e test validating equals-sign, space-separated, comma-separated, andmise runordering behavior.Reviewed by Cursor Bugbot for commit 5c3df3b. Bugbot is set up for automated code reviews on this repo. Configure here.