USAGE_DEBUG, USAGE_TRACE and USAGE_SHELL_<SHELL> are settings usage reads. usage_<name> is how usage exports a parsed arg. Windows environment variables are case-insensitive, so these are one namespace there, and two things follow from it.
A script cannot read its own arg
#!/usr/bin/env -S usage bash
#USAGE flag "--debug" help="ordinary flag name"
echo "lower usage_debug=[$usage_debug]"
echo "upper USAGE_DEBUG=[$USAGE_DEBUG]"
$ usage bash debug.sh --debug
lower usage_debug=[true] # fine
upper USAGE_DEBUG=[]
$ USAGE_DEBUG=0 usage bash debug.sh --debug
lower usage_debug=[] # the script's own flag is gone
upper USAGE_DEBUG=[true]
Windows keeps the existing key's spelling and replaces only the value, so writing usage_debug lands in USAGE_DEBUG. The script then reads $usage_debug — the name the docs give it — and finds nothing. The value is not lost, just under a spelling the script does not look for.
It needs the variable to be set already, which makes it intermittent: it works on a clean machine and fails for whoever has USAGE_DEBUG in their profile. Colliding names are debug, trace, shell_bash, shell_zsh, shell_fish, shell_pwsh.
I do not think this half is fixable without moving the namespaces apart. Command::env_remove followed by Command::env keeps the inherited spelling, and Windows cannot hold usage_debug and USAGE_DEBUG as separate variables at all:
env_remove("usage_debug") + env("usage_debug", "true") -> child sees USAGE_DEBUG=true
env("usage_debug", "true") -> child sees USAGE_DEBUG=true
Settings cannot reach a mise task
mise clears usage_* before running a task, because those variables are its own argument parser's output and must not influence spec discovery. On Windows the match is case-insensitive, which is correct for a platform where usage_foo and USAGE_FOO are one variable — but it takes the settings with it:
mise run <task> -> USAGE_SHELL_BASH=[] USAGE_FOO=[] usage_lower=[] MYAPP_SHELL_BASH=[d]
mise exec -- ... -> USAGE_SHELL_BASH=[a] USAGE_FOO=[b] usage_lower=[c] MYAPP_SHELL_BASH=[d]
So USAGE_SHELL_BASH cannot be delivered to a mise task, which is how I ran into this: usage's own Windows CI has to call cargo test rather than mise r test.
mise is not doing anything wrong here. It avoids the same trap for itself by keeping its settings under MISE_* — 301 settings, none prefixed USAGE_ — disjoint from the usage_* its parser writes.
If the settings move
Any name beginning USAGE_ is out: mise compares the first six characters ("usage_".len()) case-insensitively, so USAGE_SETTING_*, USAGE_CLI_* and USAGE__* are all still cleared. The underscore right after USAGE is what makes it unavoidable.
USAGECLI_* is the least-bad spelling I found — not pretty, but accurate: all three settings are read only in cli/src (env.rs, main.rs), so they are CLI settings rather than library ones. That would make the collision structurally impossible and let mise tasks receive them again.
It is a breaking change, and 5.0.0 has just shipped, so it may be worth parking until the next major rather than spending one on it. Documenting the reserved names would cover the "walked into it unknowingly" case in the meantime — happy to send that as a PR, and the rename too whenever you want it.
Measured on Windows 11 with usage 5.0.0.
This issue was generated by Claude Code.
USAGE_DEBUG,USAGE_TRACEandUSAGE_SHELL_<SHELL>are settings usage reads.usage_<name>is how usage exports a parsed arg. Windows environment variables are case-insensitive, so these are one namespace there, and two things follow from it.A script cannot read its own arg
Windows keeps the existing key's spelling and replaces only the value, so writing
usage_debuglands inUSAGE_DEBUG. The script then reads$usage_debug— the name the docs give it — and finds nothing. The value is not lost, just under a spelling the script does not look for.It needs the variable to be set already, which makes it intermittent: it works on a clean machine and fails for whoever has
USAGE_DEBUGin their profile. Colliding names aredebug,trace,shell_bash,shell_zsh,shell_fish,shell_pwsh.I do not think this half is fixable without moving the namespaces apart.
Command::env_removefollowed byCommand::envkeeps the inherited spelling, and Windows cannot holdusage_debugandUSAGE_DEBUGas separate variables at all:Settings cannot reach a mise task
mise clears
usage_*before running a task, because those variables are its own argument parser's output and must not influence spec discovery. On Windows the match is case-insensitive, which is correct for a platform whereusage_fooandUSAGE_FOOare one variable — but it takes the settings with it:So
USAGE_SHELL_BASHcannot be delivered to a mise task, which is how I ran into this: usage's own Windows CI has to callcargo testrather thanmise r test.mise is not doing anything wrong here. It avoids the same trap for itself by keeping its settings under
MISE_*— 301 settings, none prefixedUSAGE_— disjoint from theusage_*its parser writes.If the settings move
Any name beginning
USAGE_is out: mise compares the first six characters ("usage_".len()) case-insensitively, soUSAGE_SETTING_*,USAGE_CLI_*andUSAGE__*are all still cleared. The underscore right afterUSAGEis what makes it unavoidable.USAGECLI_*is the least-bad spelling I found — not pretty, but accurate: all three settings are read only incli/src(env.rs,main.rs), so they are CLI settings rather than library ones. That would make the collision structurally impossible and let mise tasks receive them again.It is a breaking change, and 5.0.0 has just shipped, so it may be worth parking until the next major rather than spending one on it. Documenting the reserved names would cover the "walked into it unknowingly" case in the meantime — happy to send that as a PR, and the rename too whenever you want it.
Measured on Windows 11 with usage 5.0.0.
This issue was generated by Claude Code.