An auto-review agent flagged a one-line change on a claude-config PR, and I almost dismissed it. The PR refactored how my agent fleet discovers which accounts it can use — a roster of credential files sitting on disk, so each box knows which Claude Code logins it’s allowed to rotate through. The loop that reads them looked completely fine:
for f in "$BASE"/credentials-*.json; do
[[ -f "$f" ]] || continue
# ...load the account
done
Here’s the setup for anyone who doesn’t live in a terminal: shell-init.sh is a file that runs every time a shell starts, defining functions and shortcuts I use all day. A glob is a wildcard — credentials-*.json means “every file that looks like this.” And the loop above has a guard, [[ -f "$f" ]] || continue, that’s supposed to skip anything that isn’t a real file. So if there are zero credential files, the loop just does nothing. Right?
Wrong, under zsh, and only on some machines.
zsh has a default called NOMATCH. When a glob matches no files at all, zsh doesn’t hand the loop an empty list — it throws an error at the moment it tries to expand the wildcard. That’s before the loop body runs. So my clever [[ -f "$f" ]] guard never even got a chance to fire. And because the file was being sourced — loaded into the running shell — that error aborted the entire source. Every function and alias defined after that loop silently never loaded.
The reason I’d never seen it: on my machines, credential files existed, so the glob matched and everything worked. bash also hides the bug — when a bash glob matches nothing, it leaves the literal string credentials-*.json sitting there, the guard catches it, life goes on. It only bites on a box running zsh with no matching files. Which is exactly the fresh-provisioned state a new fleet node starts in.
The fix is one line: tell the shell an empty glob is fine.
The fix and the test that actually proves it give me the detail
Set null-glob behavior locally so it doesn’t leak into the rest of the sourced environment:
# zsh
setopt local_options null_glob
for f in "$BASE"/credentials-*.json; do
[[ -f "$f" ]] || continue
load_account "$f"
done# bash equivalent
shopt -s nullglobThe part I want to underline: shellcheck passed this file the whole time. A linter reads syntax; it does not know your runtime’s NOMATCH policy or which box has zero files. The only thing that catches this is sourcing the file in a real subshell against a zero-file fixture and asserting a function defined after the loop still exists:
BASE=$(mktemp -d) # empty on purpose
zsh -c "source ./shell-init.sh; typeset -f my_late_function >/dev/null" \
&& echo PASS || echo "FAIL: tail of file never loaded"Test the empty directory, not the populated one.
What still bugs me is how quiet it was. No error surfaced to me — the shell just came up missing half its tools, and I’d have blamed something else entirely. The general lesson I took: when a file’s job is to discover things that might not be there yet, the zero-case is the primary case, not the edge case. Write the test for nothing before you write the test for something.
And keep the review agent that reads your happy-path code and asks about the empty directory. Mine earned its keep on one line.