Skip to content

fix: zsh portability, store integrity, and test coverage (3.1.1) - #13

Open
Jintin wants to merge 2 commits into
masterfrom
fix-zsh-portability
Open

fix: zsh portability, store integrity, and test coverage (3.1.1)#13
Jintin wants to merge 2 commits into
masterfrom
fix-zsh-portability

Conversation

@Jintin

@Jintin Jintin commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Three zsh-only bugs, one of which corrupts the alias store, plus the test coverage that would have caught them. All findings were reproduced against master (post-#12) before fixing, and every fix is verified in both bash and zsh.

1. _add corrupted the store under zsh (data loss)

Records were written with echo, and zsh's echo expands backslash escapes. Storing a command containing \n wrote a real newline into ~/.aliasme/cmd, breaking the two-lines-per-record format and orphaning every entry after it.

Reproduced on master under zsh:

$ al add box 'printf "[%s]\n"'
$ al add after "echo STILL_HERE"
$ al ls
box : printf "[%s]
" : after
$ al after
not found: after          # the entry exists but is unreachable

All writes and all rendering of stored text now use printf. _list had the same defect on the display side, so al ls mangled any command containing a backslash.

2. A literal ? in a command failed under zsh

zsh aborts on a glob that matches nothing, so any stored command with a query-string URL died:

# zsh, on master
$ al api                  # api = echo curl http://api.com/x?key=1
(eval):1: no matches found: http://api.com/x?key=1     exit=1
# bash, same alias
curl http://api.com/x?key=1                            exit=0

_excute now sets no_nomatch with local_options so it auto-restores. Intentional globs still expand — there's a test for that.

3. _excute's locals leaked into the eval'd command

Introduced by #12, which named them name/value — common enough that a user's own alias would collide:

$ name=MY_OWN_VALUE
$ al show                 # show = echo name=[$name] value=[$value]
name=[show] value=[echo name=[$name] value=[$value]]

Renamed to _al_*, and the command now runs after the storage file descriptor is closed rather than inheriting fd 9.

Tests

The suite previously printed failures and exited 0, so CI was green no matter what — verified by fault injection. It also wrote to the developer's real ~/.aliasme.

Now it uses a mktemp store with an EXIT trap, exits non-zero on failure, and covers what was untested: the name/command collision and exit-code propagation fixed in #12, plus escape handling, literal ?, glob expansion, and variable isolation from this PR. 19 assertions, passing in bash and zsh.

CI runs the suite on {bash, zsh} x {ubuntu, macos} — the zsh legs are the point, since every bug here is zsh-only and the primary install channel is Homebrew on macOS. ShellCheck moved to its own job.

Test plan

  • bash test/aliastest.sh and zsh test/aliastest.sh both exit 0 (19/19)
  • Fault injection: breaking _list makes the suite exit 1
  • Each of the three bugs reproduced on master, then confirmed fixed

🤖 Generated with Claude Code

Jintin and others added 2 commits August 3, 2026 00:12
Storage was written with echo, which expands backslash escapes under zsh.
Adding `printf "[%s]\n"` wrote a real newline into ~/.aliasme/cmd, breaking
the two-lines-per-record format and orphaning every entry after it. All
writes and all rendering of stored text now use printf.

Also fixes two zsh-only execution bugs and one variable leak:

- A stored command containing an unmatched glob character aborted under zsh
  with "no matches found" (any URL with a query string). _excute now sets
  no_nomatch locally; intentional globs still expand.
- _excute's locals were visible to the eval'd command, so an alias
  referencing $name or $value saw aliasme's internals. They are now _al_*
  prefixed. The command also runs after the storage fd is closed.

Tests: run against a mktemp store instead of the real ~/.aliasme, exit
non-zero on failure (the suite previously printed failures and exited 0,
so CI was green regardless), and cover the name/command collision, exit
code propagation, escape handling, and variable isolation cases. CI now
runs them on bash and zsh across ubuntu and macos, with ShellCheck split
into its own job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
assert.sh declares expected/actual as arrays, so reusing those names for
scalars in check() triggered SC2178/SC2128.

The empty-name case calls _add with no arguments, which prompts
interactively; without stdin redirected the suite blocked forever when
run from a terminal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant