Monitor tool: example scripts assume bash word-splitting, break silently or loudly under zsh (the common macOS login shell)

Open 💬 0 comments Opened Jul 6, 2026 by laurigates

Summary

The Monitor tool's example scripts (and its own tool-description guidance) assume bash-style unquoted-variable word-splitting, but Monitor scripts execute under the user's actual login shell — which on macOS is very commonly zsh, and zsh does not word-split unquoted parameter expansion by default (that's bash/POSIX-sh behavior; zsh requires ${=var} or setopt shwordsplit). Following the tool's own example patterns literally under zsh produces silent or loud failures.

Two concrete failures from one session

1. Silent corruption (worse than a loud error). A script built a space-joined list and iterated it twice:

repos_prs="repoA:66 repoB:70 repoC:47"
for rp in $repos_prs; do ...; done   # run once to poll
for rp in $repos_prs; do ...; done   # run again to report

Under zsh, for rp in $repos_prs treats the entire unquoted string as one token, so the loop body ran once with the whole string, not once per entry. The resulting notification read as valid output but mismatched fields from different list entries (e.g. printed a repo name from the first entry paired with a PR number belonging to a different entry). This was only caught by manually re-querying each item directly — the monitor's own report was silently wrong, not obviously broken.

2. Loud failure from a shell-reserved name. A second script used the bash-idiomatic local variable name status:

status=$(gh run list ... --jq '.[0].status')

zsh reserves status as a special variable (an alias for $?) and refuses reassignment: (eval):6: read-only variable: status. The whole monitor script died with exit 1.

Root cause

Both are instances of the same gap: Monitor's tool description/examples are written assuming bash semantics (unquoted multi-word variable iteration, generic variable naming), but nothing in the tool's guidance flags that the actual execution shell is whatever the user's login shell is — commonly zsh on macOS (the default since Catalina).

Suggested fixes

  • Recommend (or default to) explicit bash -c '...' invocation, or a #!/usr/bin/env bash first line, for any Monitor script that relies on bash-specific word-splitting or array semantics — the tool description doesn't currently flag this portability gap.
  • Avoid reserved zsh variable names (status, pipestatus, etc.) in the tool's own example loops, since users copy these patterns directly.
  • More generally, prefer patterns that work identically in both shells: arrays (repos_prs=(a b c)) or newline-delimited + while read -r, instead of unquoted multi-word string iteration.

Environment

  • macOS, zsh login shell
  • Observed while using Monitor to poll GitHub PR CI-check status across ~5-7 repos in parallel loops during a multi-repo PR-merge sweep

View original on GitHub ↗