Build1 publisher2 min readPublished
Naming a zsh loop variable path left PATH with exactly one entry
In one day of post-deploy checks, a link checker also flagged six Japanese tag URLs as broken by comparing percent-encoded hrefs against raw filenames, and a third failure cleared itself in 20 seconds.
The Engineer · Build desk
What happened
- A zsh verification loop named its variable path, which rewrote PATH to the single entry dist/index.html, so every command lookup after that failed inside the loop meant to hash the build output.
- On the first check after one deploy, the new article page and its markdown mirror returned HTTP 404 while the top page and indexes returned HTTP 200 with the old content.
- Instead of pushing again, the author waited 20 seconds and re-verified, and all 10 surfaces returned HTTP 200 with SHA-256 matching the local dist.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Variable naming in zsh is a correctness rule: cdpath, fpath, manpath and module_path are tied the same way as path, so any script that reaches for a bare lowercase common noun can break its own command lookup.
- cost It costs debugging time for whoever is on call, because the shell reports `command not found: shasum` and sends the investigation into filenames and PATH exports before anyone suspects the word they just typed.
- decision The check now has to distinguish failing once from failing twice: a fixed wait and a retry before anything escalates, with only the still-failing surfaces reported as an incident.
- precedent Redeploying during propagation teaches the wrong lesson, and the author expects the false causality to be recorded and the pointless push repeated on every deploy after that.
The proof is one command. `typeset -p path` prints `typeset -aT PATH path=( /opt/homebrew/bin /usr/local/bin /usr/bin /bin ... )`, and the `-T` flag is what ties the lowercase array to the uppercase scalar [4]. So `for path in dist/index.html dist/rss.xml` rewrites PATH. Renaming the variable to `file_path` restores command lookup, and each file's SHA-256 prints normally [6].
Declaring it `local` inside a function only protects the caller. According to the post, PATH outside the function is restored when the function returns, but PATH is still broken while the body runs, so you cannot call commands in there [8].
The second failure was in the comparison code. The link check pulls hrefs out of the production HTML and matches them against the generated files in `dist`. Production served `/tags/%E7%B1%B3%E5%9B%BD%E7%B5%8C%E6%B8%88/` while the local list still held `/tags/米国経済/` [10]. After decoding and re-comparing, all 21 unique internal links returned HTTP 200 [11]. Six flagged out of 21 checked is about 29 percent, and every breakage the checker reported in that batch was its own encoding mismatch [1].
The author sorts the three causes as a bug in the verification script, a bug in the comparison logic, and propagation delay on the delivery side [17]. Two of the three sat in the checking code [2]. The post describes what the script is supposed to confirm after each deploy: that all 10 public surfaces return HTTP 200 and that their contents match the local build output (dist) [1].
This is one day of one author's verification on one static site [2], and the figures do not all travel the same way. The 20 second recovery is a single observation of one delivery path, so a retry threshold copied from it is a guess about someone else's cache. The encoding mismatch needs non-ASCII characters in the URLs to appear at all: the two strings differed only because the tag was Japanese [10]. The tied-array behaviour is different in kind, because it depends on the shell you wrote the check in, and `typeset -aT PATH path` is true of every zsh on the machine [4].
What to watch
- Whether a genuine 404 survives the fixed retry window, since the 20 second wait comes from one observation of one delivery path.
- Whether normalizing by decoding holds up for other non-ASCII URL forms; the fix was exercised on Japanese tag paths.
- Whether the same trap resurfaces in scripts that use cdpath, fpath, manpath or module_path as loop variables.