Skip to content

Instantly share code, notes, and snippets.

@xpe
Last active December 13, 2025 16:01
Show Gist options
  • Select an option

  • Save xpe/6881bc145ce7661d5e1ddb8a2c79ed35 to your computer and use it in GitHub Desktop.

Select an option

Save xpe/6881bc145ce7661d5e1ddb8a2c79ed35 to your computer and use it in GitHub Desktop.
Discussion with Claude 4.5 Opus about https://github.com/tristanisham/zvm/issues/143

No, this is bad UX. The asymmetry violates the principle of least surprise.

What --all conventionally means: A flag like --all should modify the scope or filtering of output, not the fundamental preconditions for the command to execute. Users expect flags to be additive or parametric modifications to a base behavior.

Examples of expected behavior:

Command Without flag With --all / -a
ls vs ls -a Shows visible files Also shows hidden files
docker ps vs docker ps -a Shows running containers Also shows stopped containers
git branch vs git branch -a Shows local branches Also shows remote branches

In all these cases, the preconditions are identical—the flag just widens the filter.

The problem with zvm's behavior:

If zvm ls requires zig but zvm ls --all doesn't, then --all is secretly switching between two fundamentally different operations:

  • One queries the local zig installation
  • One queries some remote/available versions list

These should arguably be separate commands (e.g., zvm ls vs zvm ls-remote or zvm available), or the base command should work without zig and show local installs as a subset marked differently.

Your framing is correct: this is a good "narrowest point" to anchor the design discussion because resolving it forces clarity about what ls fundamentally does.

Your intuition is correct. This is poor UX.

The principle being violated: Flags should modify what a command does (scope, format, verbosity), not whether it can run at all. A flag that changes preconditions breaks the mental model users have of flags as modifiers on a base operation.

Standard examples that follow good UX:

  • ls vs ls -a — both require directory access; -a just includes hidden files
  • git branch vs git branch -a — both require a git repo; -a adds remote branches
  • docker ps vs docker ps -a — both require the daemon; -a includes stopped containers
  • ps vs ps -a — both work; -a shows other users' processes

A counterexample that also feels wrong (for the same reason):

Imagine if curl worked without network access when you passed --dry-run, but required it otherwise. You'd expect --dry-run to skip the network call, not to enable running without network capability in the first place.

What this asymmetry suggests architecturally: ls and ls --all are probably implemented as two fairly different code paths—one queries a remote/local zig installation, the other just fetches a list from somewhere else (registry, hardcoded, etc.). The flag isn't filtering; it's selecting between two different operations that happen to share a name.

Your framing—starting from this narrow asymmetry to "lock in" design constraints—is a reasonable way to scope the discussion.

No, this is poor UX. Flags should modify what or how much a command outputs, not change whether it can run at all.

The violated expectation: --all conventionally means "show me more of the same kind of thing" (additive/expansive), not "do a fundamentally different operation with different prerequisites."

Good examples of --all or similar:

  • ls -a — shows hidden files too. Both versions require the directory to exist.
  • git branch vs git branch --all — shows remote branches too. Both require a git repo.
  • docker ps vs docker ps -a — shows stopped containers too. Both require docker running.
  • pip list vs pip list --outdated — filters differently. Both require pip/environment.

What's actually happening semantically:

  • zvm ls = "list installed versions" (local state)
  • zvm ls --all = "list available versions" (remote state)

These are conceptually different operations. One queries local state, the other queries a remote registry. Cramming them under one command with a flag obscures this.

Better designs:

  • zvm ls (local) and zvm ls --remote or zvm available (remote)
  • Or: zvm ls should work with zero installations (return empty list), so prerequisites align

Your draft correctly identifies this as an asymmetry worth addressing. The flag naming (--all) also misleads—it suggests "all installed" not "all existing anywhere."

Look harder. Are you sure you can't find tools that bend or break this "rule" you've described. Do not waffle between inconsistent points. If you find a tension, analyze and synthesize.

And, of course, surveying "what exists" is not a definitive substitute for a good design sense.

You're right to push. Let me look harder.

Actual counterexample: git config outside a repo fails, but git config --global outside a repo succeeds. Different scopes, different prerequisites. Widely used, generally accepted.

Why git gets away with it: The flag (--global) explicitly names a different operational domain. The semantics are transparent: "global" obviously doesn't require repo context.

Why zvm's --all doesn't get the same pass: --all semantically means "remove filtering / show more of the same kind of thing." It doesn't signal "query a different data source." The flag name lies about what it does.

So the issue isn't that flags can't change prerequisites—they can, when the flag explicitly describes a scope change (--global, --remote, --system, --offline). The issue is using --all to do this, because --all carries strong conventional meaning about quantity/filtering, not operational mode.

The actual design failure is twofold:

  1. Two distinct operations (query local state vs. query remote registry) crammed into one command
  2. The flag chosen (--all) misrepresents what's actually changing

A secondary issue: zvm ls with zero installations should return an empty list, not fail. The prerequisite itself seems spurious.

Your original draft correctly identifies the asymmetry as problematic. The deeper problem is semantic: the flag name doesn't match the operation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment