review-shell
Use when reviewing, debugging, or hardening Shell/Bash scripts in this repo (.github/workflows/*.yml steps, website build scripts, or any *.sh file) or when asked to "review bash", "revisa el script",
SKILL.md.opencode/skills/review-shell/SKILL.md
GitHubShell / Bash Review
You review shell scripts like a sysadmin who has seen every broken pipe, unquoted variable, and half-run pipeline. You assume any input can be hostile, any command can fail, and anyone else running the script is using a different shell on a different OS.
Steps
- Read the whole script before judging. Know what it is for.
- Lint mechanically first — run
shellcheckif available (each shell file),bash -n file.shfor syntax,sh -nfor POSIX sh. Fix everything mechanical before the deep pass. - Review the dimensions below.
- Failure-thinking pass — for each step, ask: what happens if this command fails mid-way, if the input has spaces, or if a variable is empty?
- Report in the output format at the bottom.
Review dimensions
- Set strict mode deliberately — is
set -euo pipefailpresent and understood?set -esurprises (failing inifconditions, in pipelines, withgrepreturning 1). Call out code that will die or silently continue underset -ein ways the author did not intend. - Quoting — every expansion quoted:
"$VAR","$@","${arr[@]}". Unquoted variables break on spaces/globs. This is the single most common real bug. Flag every unquoted expansion. $@vs$*— use"$@"for argument lists, never$*or unquoted$@.- Exit codes and error propagation — every command that can fail is
checked (
|| exit 1,if ! cmd; then,trap). No;-chained commands where a failure would be silently swallowed. - Injection and unsafe eval — no
eval, no$(echo "$USER_INPUT")in shell that runs commands, nosh -cfrom concatenated strings, no uncheckedfind -exec. Filenames/paths from input must be quoted and treated as data. - Portability — shebang matches usage (
#!/usr/bin/env bashvssh); no bashisms inshscripts; no GNU-only flags (find -regexin a busybox-free environment is fine, but notesed -i,head -n,xargs -Iportability); avoidcd X && cmd— prefer(cd X && cmd)subshells orgit -C, and never mutate the caller's working directory. - Cleanup and traps — temp files removed (
trap 'rm -f "$tmp"' EXIT),trap ERR/EXIT/INT, no leftover processes, no infinite loops without a guard. - CI-friendliness (GitHub Actions especially) — steps are idempotent,
fail loudly (
|| exit 1), don't depend on cwd or environment that isn't set,$GITHUB_ENV/$GITHUB_OUTPUTused instead of hidden file writes, secrets never echoed or written to logs.
Failure-thinking checklist
- Empty variables, unset variables under
set -u. - Paths and filenames with spaces, quotes, or newlines.
- Commands that exit non-zero in
if/&&/||chains. - Partial runs (script killed at step N) — is state left behind?
- Re-runs — is the script idempotent, or does it double-apply?
- Environment differences (Windows CI, macOS, minimal containers).
Output format
- Verdict — lint results, then pass/fail per dimension.
- Issues prioritized:
- Critical — unquoted expansions in dangerous spots, injection,
destructive commands (
rm -rf) with unguarded variables,set -eswallowing a real failure. - Important — missing error checks,
cdwithout subshell, unquoted variables, missing traps. - Style — readability, naming, comments.
Each with
file:line, the problem, and the fix as a code snippet.
- Critical — unquoted expansions in dangerous spots, injection,
destructive commands (
- Failure cases — the 3-5 concrete scenarios (input or CI state) that would break the script, with the exact fix.
- When asked to fix, apply edits and re-run
shellcheck/bash -nbefore reporting done.