Bash Expert

Caution

Missing error handling, unquoted variables and hardcoded paths can break scripts on another machine. Bash Expert follows repository patterns and checks syntax, ShellCheck findings and error paths.

Tip

Trigger by describing the script you need. “create a setup script for my plugin”, “bash script to check homebrew services”, “install script for macOS and Linux” — the agent picks the right template, adds error handling, and validates syntax before handing back the file.

Current execution contract

Bash Expert writes and validates a bounded script itself. Missing decisions return to the main caller; it does not spawn nested agents. Its maxTurns: 60 is an anti-loop stop, with partial results and on-disk checkpoints for recovery.

Quick reference

FieldValue
Modelinherit — runs on the session’s model
ToolsRead, Write, Edit, Glob, Grep, Bash, WebFetch
Triggers”create script”, “bash script”, “shell script”
Output.sh file + bash -n + ShellCheck report

Scope guard

Bash Expert sizes the task before writing. One bounded unit = one deliverable, roughly 5 files, roughly 10 steps.

SituationWhat the agent does
Brief fits one bounded unitStarts immediately
Brief exceeds ~5 files / ~10 steps, or bundles several independent deliverablesStops before starting, returns a split proposal: 2-N bounded subtasks, each with scope and a suggested owner
Scope grows mid-flightStops at the next clean boundary, reports done / remaining / how to split
Brief is missing GOAL, SCOPE, CONTEXT, CONSUMER or acceptanceStates a safe assumption or returns the unresolved question to the main caller; never invents scope

“Write the whole CI toolchain” gets a split proposal. “Write the install script for brewtools” gets a script.

Ordinary subagents cannot use AskUserQuestion; conversation forks retain the parent’s tool pool. This ordinary delegated agent returns decision requests to its caller. Claude Code allows depth-limited nested delegation, but Brewcode dispatches through the main conversation using the Agent tool. Bash Expert’s own tool list contains no Agent.

On maxTurns exhaustion, Claude Code 2.1.246+ returns a partial result. The caller inspects it and resumes with SendMessage rather than marking the work complete. After ShellCheck and a smoke run, Bash Expert records each script’s path and status in .claude/reports/YYYYMMDD-HHMMSS_bash-expert/report.md; it reads that checkpoint first on resume.

Tip

Got a split proposal instead of a script? That is the guard working. Pick one script from the proposal and re-send it, or spawn the subtasks as parallel agents in a single message.

When to use

  • Plugin lifecycle scripts — setup, install, teardown, version-bump for brewcode/brewtools/brewui/brewdoc
  • CI helper scripts — health checks, service validation, environment bootstrap
  • Cross-platform portability — script must run on both macOS (ARM + Intel) and Linux without modification
  • Structured output — script prints markdown tables or phase headers consumed by Claude Code skills
  • Idempotent installers — safe to re-run; checks state before mutating

Examples

# Natural language triggers
"create a setup script for the brewtools plugin"
"bash script to check if all homebrew services are running"
"write an install script that works on macOS and Linux"
# Multi-mode dispatch
"write a multi-mode script with status/install/help commands"
# Plugin-aware paths resolve via ${CLAUDE_PLUGIN_ROOT}
"create a version-bump script for the plugin"

Flow

  1. Analyze the request

    Reads existing scripts in the repo (Glob + Grep) to match conventions. Identifies target platform, required commands, and expected output format before writing a single line.

  2. Choose a template

    Minimal (single-purpose, ≤20 lines) or full multi-mode dispatch (`CMD="${1:-help}"` + case block). Plugin scripts always get SCRIPT_DIR self-location and correct plugin-root resolution.

  3. Implement

    Writes the script with set -euo pipefail, quoted variables and trap cleanup EXIT where needed. Uses compact status circles and plain warning text; check/cross markers remain optional. Platform detection handles macOS/Linux differences.

  4. Validate

    Runs bash -n script.sh (syntax check) and shellcheck script.sh (static analysis). Fixes all ShellCheck warnings before reporting.

  5. Report

    Prints a structured summary: file path, purpose, platform, and a checklist of verified properties. You get a ready-to-run script, not a draft.

Internals — patterns, templates, and platform handling

Conventions

PatternExampleUse
Strict modeset -euo pipefailEvery script, by default
Cleanup traptrap cleanup EXITTemp files, locks, other resources
Required var${VAR:?error msg}Mandatory input
Soft failurecmd || echo "Warning: optional step failed" >&2Optional steps

Mode detection

ARGS_LOWER=$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')
case "$ARGS_LOWER" in
  status|install|upgrade|enable|disable|uninstall|purge) MODE="$ARGS_LOWER" ;;
  '') MODE="default" ;; # Resolve using this setup's documented default.
  *) echo "Unsupported mode: $ARGS_LOWER" >&2; exit 2 ;;
esac

Minimal template

#!/bin/bash
set -euo pipefail
ARG="${1:-}"
[[ -z "$ARG" ]] && { echo "Usage: script.sh <arg>"; exit 1; }
echo "Processing: $ARG"
echo "✅ Done"

macOS vs Linux divergences

FeaturemacOSLinux
Brew prefix/opt/homebrew (ARM), /usr/local (Intel)/home/linuxbrew/.linuxbrew
timeoutgtimeout (coreutils)timeout
sed in-placesed -i ''sed -i
readlinkgreadlink -freadlink -f

Always derive prefix via $(brew --prefix) — never hardcode.

Structured output

ElementPattern
Status symbols🟢 success · 🔴 error/blocker · ⚪ pending/skipped · 🔵 active/updated; warnings use Warning:
Markdown tableecho "| Component | Status |" then the separator row, then data rows
Phase headerecho "=== Phase 1: Scanning ===" && echo ""

JSON parsing fallback chain

Use a real JSON parser and pass the input file explicitly. Regex extraction is not a JSON parser, and macOS’s system grep does not support -P.

if command -v jq >/dev/null; then
  jq -r '.key' file.json
elif command -v python3 >/dev/null; then
  python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["key"])' file.json
else
  echo "need jq or python3" >&2; exit 1
fi

Plugin path variables

VariableAvailable in
${CLAUDE_PLUGIN_ROOT}Claude expands plugin skill/agent text and hook/MCP commands; ordinary scripts must receive or derive the value
${CLAUDE_SKILL_DIR}Skills (string substitution)
$PLUGIN_ROOT/skills/X/scripts/Scripts that explicitly define or receive PLUGIN_ROOT

Plugin agent text receives root substitution at Agent spawn. Project-local agents receive no plugin-root substitution. Prompt tokens do not guarantee corresponding shell environment variables; standalone scripts locate themselves with SCRIPT_DIR and derive or accept their root.

Anti-patterns

AvoidPrefer
[ $VAR ][[ -n "$VAR" ]]
cat file | grepgrep X file
ls | while readfind -exec or glob
cd dir; cmd; cd -(cd dir && cmd)
echo $VARecho "$VAR"
if [ $? -eq 0 ]if cmd; then
/usr/local hardcoded$(brew --prefix)

Delivery checklist

#Check
1#!/bin/bash shebang
2set -euo pipefail
3Usage header comment
4shellcheck clean
5chmod +x applied
6bash -n passes
7help subcommand works
8Error paths tested
9Idempotent — safe re-run

Return Contract

Verdict first, <=30 lines, path:line. No script bodies, no ShellCheck transcripts, no smoke-run output, no preamble — one block per script, nothing else (the format is in Flow step 5 above). Failures return the check that failed plus the offending path:line, never the full output; long logs and full ShellCheck runs go to .claude/reports/<YYYYMMDD-HHMMSS>_bash-expert/, path only.

/brewtools:agent-return-setup enforces this at ~1000 / ~2500 est-tokens when installed; the contract itself ships unconditionally.

🔗

hook-creator agent

Builds Claude Code hooks — pairs with bash-expert when a hook shells out.

🔗

GitHub source

Agent definition, system prompt, and tool configuration.

📄

Brewcode overview

All brewcode skills and agents in one place.

Updating plugins

Use /brewtools:plugin-update to check and update the brewcode plugin suite in one command. See the FAQ for details.