Shell Development
In GitHub Actions, this skill owns the shell inside run: blocks; operating-infra owns the workflow YAML around it (jobs, permissions, actions, secrets, caching). Mixed changes use both.
Shell Choice
- Follow the existing shebang and tooling. New portable scripts: POSIX
shfor simple logic; Bash when you need arrays,pipefail,[[ ]], or regex. - Zsh and Fish only for existing files, interactive config, or explicit requests.
- Never rely on the agent's own shell. Name the shell in the shebang, and invoke it explicitly in tests.
- Shell is glue. Move data modeling, business logic, or CPU-heavy work to the project's real language.
Safety and Portability
- Bash:
set -euo pipefail, knowing it does not fire inside conditionals or&&/||lists. POSIX sh:set -euand explicit pipeline checks. - Build commands with arrays, never strings plus
eval. Put--before user-controlled operands. - NUL- or newline-safe loops for filenames; never parse
lsor usefor x in $(cmd). mktempplus a cleanuptrapfor temp files, locks, and partial outputs.- No
curl | sh: download, verify, then run. - Destructive actions list their targets first and require confirmation unless the script runs non-interactively with explicit inputs.
- macOS/BSD vs GNU: avoid
sed -i,date -d,readlink -f, and GNU-onlygrepflags unless the dependency is documented. Preferprintftoecho. - Every
shellcheck disablecarries a short reason.
CLI Tools
- Use non-interactive, pipe-friendly tools with stable stdout and exit codes: no TUI, pager, color, or prompts when output is consumed.
- Parse structured data with
jq,yq,mlr, ordasel, notgrep/sedscraping. Search withrgandfd. - Preview replacements before applying them (
sd -p,rg --replace). - Check for non-standard tools and fail with a clear message. Never install them silently.
- Look up exact flags with looking-up-docs instead of guessing.
References
- testing.md: gate commands (shfmt, ShellCheck, checkbashisms, Bats, ShellSpec) and test design; read when adding tests or choosing checks.
Done when the relevant build/test/lint checks pass on what you changed, or you name each check that did not run and why.