Shell
Execution substrate for governed agentic workloads; behavioral contracts, action budgets, pluggable memory, and portable skill bundles
npx -y skills add rmednitzer/agents --skill shellAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Author robust, safe Bash and run commands reliably on local and remote machines. Use when writing or reviewing shell scripts (strict mode, quoting, word splitting, ShellCheck, error and signal handling, traps, retries, timeouts, locking, concurrency, idempotent and atomic writes); when automating SSH (ssh_config model, keys and agent, host-key trust and certificate authorities, ProxyJump bastions, ControlMaster multiplexing, BatchMode, no-PTY exit-status semantics, scp and rsync, authorized_keys constraints, sshd hardening); or when choosing a reliable mechanism to run or drive a process instead of a fragile scripted session (direct exec, setsid, systemd-run, script, expect). Triggers on bash, sh, shell script, shebang, ShellCheck, heredoc, trap, ssh, ssh_config, scp, rsync, known_hosts, ProxyJump, ControlMaster, BatchMode, expect, detached background job.
The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
14.5 KB, as published. Nobody here has run it
Shell: robust Bash and reliable command execution
Two jobs share this skill because they are usually done together and fail for the same reason (treating the shell as forgiving when it is not):
- Writing Bash that is safe under failure, untrusted input, and odd filenames.
- Running commands reliably on local and remote machines: SSH first (it is the terminal you actually automate), and otherwise the most deterministic mechanism, not a scripted interactive session.
The governing rule for job 2: the most reliable automation never allocates a
PTY. For remote work that means ssh host cmd with the right options, not
scripting an interactive login. Drop to a pseudo-terminal only when a program
genuinely requires one, and even then keep the interaction deterministic.
When to use
Use this skill when the task involves any of:
- Writing, reviewing, hardening, or debugging a shell script.
- Choosing a shebang, strict-mode flags, or quoting and deciding whether Bash is even the right language.
- Running a long job that must survive disconnect, or capturing a command's output and exit status programmatically.
- Driving a program that has no API and either prompts or needs a TTY (an
installer, a passphrase, an SSH session): choosing
expectorscriptover a fragile hand-rolled session. - Diagnosing flaky automation: a command "sometimes" not arriving, output truncated, exit status lost, a script that "succeeds" while broken.
If a real API, SDK, or library exists for the target, use that instead. Driving its CLI through a terminal is a last resort, not a default.
The decision ladder (read before automating anything)
Pick the highest rung that satisfies the requirement. Each rung down adds a failure mode (a PTY, a shell, timing).
| Rung | Situation | Mechanism |
|---|---|---|
| 1 | Non-interactive, deterministic, finishes in foreground | Run it directly. Capture rc=$?, redirect stdout/stderr to files. No wrapper. |
| 2 | Long-running, must outlive the parent or an SSH disconnect, no TTY needed | setsid/nohup, or systemd-run --user (cgroup-tracked). Write a log file and an exit-code sentinel file; poll the sentinel. |
| 3 | Strict prompt then response, or a program that requires a TTY | expect (or pexpect): allocates a real PTY and matches deterministically, no sleeps. For a program that only checks isatty() but is not interactive, script -qec 'cmd' /dev/null or unbuffer supplies a PTY with no scripting at all. |
There is no rung 4. A long-lived session a human also attaches to is an
interactive-UX concern, not reliable automation: if you need detach and
reattach use systemd-run --user --pty, GNU screen, or abduco (see
references/terminal-automation-alternatives.md), but never build automation
logic on top of scraping a rendered screen. Screen scraping has no reliable
completion or exit-status signal; rungs 1 to 3 always do.
Remote targets ride these same rungs over SSH, not a scripted login.
A remote command is rung 1: ssh host cmd runs without a PTY and returns
the command's real exit code. Make it reliable with BatchMode=yes,
ConnectTimeout, and ServerAliveInterval so it fails fast instead of
hanging; see the SSH section below and references/ssh-in-depth.md. Use
ssh -tt (force a PTY) only when the remote genuinely demands one, and
drive that with expect, not ad-hoc sleeps.
Rungs 1 to 3 and the detach/multiplexer options are covered in
references/terminal-automation-alternatives.md, including systemd-run,
script(1), expect, GNU screen, and zellij, with the reliability
trade-off for each. Rung 3 (expect) is the floor for a genuinely
interactive target; everything above it is more reliable, so do not reach
for a PTY by habit.
Non-negotiable Bash safety baseline
Every script this skill produces starts from this header. The rationale, the
real criticisms of each flag, and the mitigations are in
references/bash-safety-and-pitfalls.md; the rules below are the short form.
#!/usr/bin/env bash
# Strict mode. Know what each flag does NOT cover (see reference).
set -Eeuo pipefail
IFS=$'\n\t'
-e(errexit): exit on an unhandled non-zero. It is full of exceptions (the left side of&&, any command in a condition, a function called in anif). It is a safety net, not a guarantee. Never rely on it as your only error handling.-E(errtrace): make anERRtrap fire inside functions, command substitutions, and subshells. Without it,set -eplus a trap silently misses errors. Always pair-Ewith anERRtrap.-u(nounset): unset variable is an error. Write"${VAR:-default}"deliberately; for arrays that may be empty under older Bash use"${arr[@]:-}".-o pipefail: a pipeline's status is the last (rightmost) command to exit non-zero, not just the final stage. Required, but it means a deliberately short read (a closed pipe givingSIGPIPE/141) now counts as failure: handle those cases explicitly.IFS=$'\n\t': stop splitting unquoted expansions on spaces. It reduces damage from a missed quote; it does not replace quoting.
The five rules that prevent the majority of shell bugs:
- Quote every expansion:
"$var","$@","${arr[@]}","$(cmd)". Unquoted means word-split then glob. The exception is a deliberate, commented split. - Never parse
ls. Neverfor x in $(...). Glob directly (for f in ./*.log) or stream NUL-delimited (find . -print0 | while IFS= read -r -d '' f). [[ ... ]]for tests,(( ... ))for arithmetic. Inside[[ ]],<and>are string comparison; use(( a > b ))for numbers.- Check what can fail:
cd "$d" || exit 1. A pipe runs its stages in subshells, socmd | while read ...; ((n++)); donecannot exportn; usewhile ...; done < <(cmd)(process substitution) instead. - Run ShellCheck. It catches most of the above mechanically. Treat
warnings as errors; justify each
# shellcheck disable=SCxxxxwith a reason on the same comment.
The canonical wrong/right table (the high-frequency pitfalls from Greg's
Wiki, condensed) is in references/bash-safety-and-pitfalls.md. Read it
before reviewing anyone's shell, including your own.
Robust script architecture (the short version)
For anything past a throwaway one-liner, follow the structure in
references/bash-robust-scripting.md and start from
assets/bash-script-skeleton.sh. The load-bearing parts:
- Cleanup is a trap, not a final line.
trap cleanup EXITruns on success, error, and signal. Makecleanupidempotent. Create scratch space withmktemp -dand remove it in the trap, never with a barerm -rf "$dir/"*where$dircould be empty. - An
ERRtrap reports where it died: log the failing command, line, and exit code to stderr, then exit. This is why-Eis in the header. - Logs and diagnostics go to stderr; only the script's actual result goes to stdout. That keeps a script composable in a pipeline. Timestamp log lines.
- Make state-changing scripts idempotent, and write files atomically
(write to a temp file on the same filesystem, then
mvinto place;mvis atomic, a half-written>redirect is not). - Retries: only retry idempotent operations, with exponential backoff plus jitter and a cap, and a distinct exit code on give-up. The exact loop (including network-only retry classification) is in the reference.
- Timeouts: wrap anything that can hang in
timeout; handle the124exit code. Bound every wait. - Concurrency: a bounded job pool (
xargs -P, GNUparallel, or await -npool) beats unbounded&. Serialize across script invocations withflockon a lock file, not an ad-hoc lock directory.
Reliable SSH automation
SSH is the terminal you actually automate. Almost every flaky "remote"
script traces back to one of the items below. Full treatment (ssh_config
match order, keys and agent, host-key trust and CAs, jump hosts,
multiplexing, forwarding, file transfer, authorized_keys constraints,
sshd hardening) is in references/ssh-in-depth.md.
The non-negotiable automation invocation:
ssh -n -T \
-o BatchMode=yes -o ConnectTimeout=10 \
-o ServerAliveInterval=15 -o ServerAliveCountMax=4 \
-o StrictHostKeyChecking=accept-new \
"$host" 'set -Eeuo pipefail; remote-cmd --flag'
rc=$? # the REMOTE command's real exit code (255 is ssh's own failure)
Why each piece, and the load-bearing rules:
BatchMode=yes: never prompt. A missing or wrong key fails immediately instead of blocking CI on a hidden password prompt forever. Pair with key or certificate auth.ConnectTimeout+ServerAliveInterval/ServerAliveCountMax: a dead or black-holed host is detected in seconds, not never. Without these, automation hangs indefinitely on a firewalled peer.StrictHostKeyChecking=accept-new: accept a new host, still refuse a changed key. Neverno(defeats MITM protection); never leave the interactive default in a script (it hangs with no TTY). Prepopulate withssh-keyscanand useyeswhen you can.-n(stdin from/dev/null):sshreads stdin, so insidewhile read h; do ssh "$h" ...; done < hoststhe firstssheats the rest ofhostsand the loop runs once.-n(or< /dev/null) unless you are deliberately piping data in.-Tno PTY; the remote command's exit status passes through.ssh -ttforces a PTY only when the remote demands one (sudorequiretty, an interactive tool); script that case withexpect.--is not a local option terminator after the host.sshstops parsing its own options at the destination, sossh host -- cmdasks the remote shell to run-- cmd. The guard goes before the host.- Remote command quoting is double expansion (local shell, then the
remote login shell). Single-quote to defer, or push a script:
ssh host bash -s -- arg < script.sh. IdentitiesOnly yeswhen an agent holds several keys, or the server'sMaxAuthTriesrejects you (Too many authentication failures) before the right key is offered.- Multiplex many connections:
ControlMaster auto,ControlPath ~/.ssh/cm/%C(use%C; the literal%r@%h:%poverflows the socket-path limit),ControlPersist 10m. Authenticate once; far faster and more reliable than N fresh handshakes. - Bastions:
ProxyJump, not agent forwarding (the bastion never sees your keys). At scale, an SSH certificate authority removesknown_hostsTOFU andauthorized_keyssprawl; lock automation keys withrestrict,command=,from=inauthorized_keys.
Picking the mechanism (one-line guidance)
- Remote command, output and exit code:
ssh host cmdwith the automation options above. Not a scripted login. - Local, output and exit code, no TTY: run it directly (rung 1).
- Survive disconnect, no TTY:
systemd-run --userorsetsid+ sentinel file. More reliable than scraping a detached session. - Prompt/response, or a program that requires a TTY:
expect/pexpect(deterministic). PTY needed but no interaction:script -qecorunbuffer. - Detach/reattach a session a human will also use:
systemd-run --pty, GNUscreen, orabduco(interactive UX, not automation). - Pure session recording for humans:
script -corasciinema.
The full matrix, with why each is more reliable for its niche, is in
references/terminal-automation-alternatives.md.
Bundled resources
References (load on demand; do not inline their full contents):
references/bash-safety-and-pitfalls.md: strict mode dissected with its real criticisms and mitigations, the canonical wrong/right pitfalls table, quoting and word-splitting,[[ ]]/(( )), ShellCheck usage and directives.references/bash-robust-scripting.md: production script architecture, stderr logging,getoptsand long options,mktempand trap-based cleanup, idempotency, atomic writes, retry with backoff and jitter, timeouts,flock, bounded concurrency, dependency preflight, dry-run.references/ssh-in-depth.md: the ssh_config first-match model, keys and agent (IdentitiesOnly, agent-forwarding risk), host-key trust (accept-new) and certificate authorities,ProxyJumpbastions,ControlMastermultiplexing, non-interactive semantics (BatchMode, stdin stealing, double quoting,--placement), forwarding,scp/rsync,authorized_keysconstraints, and ansshdhardening baseline.references/terminal-automation-alternatives.md: the decision matrix and deep-dive on direct exec,setsid/nohup/disown,systemd-run,script(1),asciinema,expect/pexpect, GNUscreen,zellij,abduco+dvtm,mosh, and SSH as a transport alternative.references/quick-reference.md: dense cheat tables (parameter expansion, conditionals, redirection, signals, and SSH automation options).
Assets (copy and adapt; they embody every rule above and are ShellCheck clean):
assets/bash-script-skeleton.sh: production template (strict mode,ERRandEXITtraps, leveled stderr logging,usage,getopts,mktempcleanup,main "$@").