Overview
This hook runs ShellCheck after Claude Code writes or edits a shell-like file.
It is intentionally read-only: it reports diagnostics and exits non-zero when
ShellCheck finds issues, but it does not run the edited script, install
packages, source files, rewrite files, or enable external-source traversal.
Use it when a project contains shell scripts and you want immediate feedback on
quoting, command substitution, globbing, portability, error handling, variable
expansion, unsafe patterns, and common shell mistakes after Claude edits a file.
Source Review
These sources were reviewed on 2026-06-04. Prefer the live ShellCheck site,
wiki, and repository over model memory for current installation options,
supported shells, directives, diagnostic behavior, rule explanations, and
configuration guidance.
Requirements
- Claude Code hooks enabled in user or project settings.
- ShellCheck installed locally and available on
PATH.
jq installed locally so the script can read Claude Code hook input.
- A reviewed hook configuration scoped to
Write, Edit, and MultiEdit.
Hook Configuration
Add the hook command to .claude/settings.json or the appropriate user-level
Claude Code settings file:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "./.claude/hooks/shellcheck-static-analysis.sh"
}
]
}
]
}
}
Hook Script
Save this as .claude/hooks/shellcheck-static-analysis.sh and make it
executable:
#!/usr/bin/env bash
set -uo pipefail
input="$(cat)"
if ! command -v jq >/dev/null 2>&1; then
echo "ShellCheck hook skipped: jq is required to parse Claude Code hook input." >&2
exit 0
fi
tool_name="$(printf '%s' "$input" | jq -r '.tool_name // .toolName // empty')"
file_path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.path // .toolInput.file_path // .toolInput.path // empty')"
case "$tool_name" in
Write|Edit|MultiEdit|write|edit|multiedit) ;;
*) exit 0 ;;
esac
if [ -z "$file_path" ] || [ ! -f "$file_path" ] || [ ! -r "$file_path" ]; then
exit 0
fi
case "$file_path" in
*.sh|*.bash|*.bats|*.ksh) ;;
*)
first_line="$(LC_ALL=C sed -n '1p' "$file_path" 2>/dev/null || true)"
case "$first_line" in
'#!'*'/sh'*|'#!'*' bash'*|'#!'*'/bash'*|'#!'*' dash'*|'#!'*'/dash'*|'#!'*' ksh'*|'#!'*'/ksh'*) ;;
*) exit 0 ;;
esac
;;
esac
if ! command -v shellcheck >/dev/null 2>&1; then
echo "ShellCheck hook skipped: install shellcheck to enable shell diagnostics." >&2
exit 0
fi
echo "ShellCheck hook: checking $file_path" >&2
shellcheck --format=gcc "$file_path"
status=$?
if [ "$status" -ne 0 ]; then
echo "ShellCheck hook: diagnostics found. Review quoting, expansion, portability, and error-handling issues before asking Claude to continue." >&2
fi
exit "$status"
What It Checks
- Files ending in
.sh, .bash, .bats, or .ksh.
- Extensionless files with a shell-like shebang for
sh, bash, dash, or
ksh.
- Only files touched by
Write, Edit, or MultiEdit.
- Only files that exist and are readable at the time the hook runs.
- Only the edited file, without enabling ShellCheck external-source traversal.
Use Cases
- Catch unquoted variables, unsafe globbing, word splitting, and command
substitution mistakes immediately after Claude edits a script.
- Review shell portability before committing scripts used by CI, release,
deployment, Docker, or local developer workflows.
- Surface failing diagnostics before a user runs a changed shell script.
- Keep hook behavior read-only so Claude can fix scripts with explicit follow-up
edits rather than automatic rewrites.
Safety Review
ShellCheck is a static analyzer. This hook invokes it against the edited file
and returns the diagnostics to Claude Code. It does not execute the script being
checked, does not source project files, does not install ShellCheck, and does
not mutate repository files.
Treat any follow-up fix as a normal code change. Shell diagnostics can point to
real defects, but they do not replace review of command intent, input trust,
filesystem effects, network calls, secret handling, and deployment impact.
Troubleshooting
Hook says ShellCheck is missing
Install ShellCheck through the operating system package manager or official
release path, then confirm command -v shellcheck works in the same shell that
Claude Code uses.
Hook never runs
Confirm the hook is configured under PostToolUse, the matcher includes
Write, Edit, or MultiEdit, and the command path points to the executable
script.
Extensionless script is skipped
Add a supported shell shebang to the first line, such as #!/usr/bin/env bash,
or rename the file with a supported extension.
Diagnostics are too noisy
Prefer fixing the script or adding reviewed ShellCheck directives near the
specific line. Avoid blanket disabling rules in the hook command because that
can hide real issues across the project.
Hook blocks the workflow
This script exits non-zero when ShellCheck reports diagnostics. If the team
wants non-blocking behavior, adjust the project hook policy explicitly rather
than silently swallowing diagnostics.
Duplicate Check
This entry was checked against the current upstream/main content tree, open
pull requests, and existing source URLs before drafting. No existing
content/hooks, content/skills, content/agents, or content/mcp entry
matches ShellCheck, koalaman/shellcheck, shellcheck.net, or a dedicated
read-only ShellCheck PostToolUse hook.
Editorial Disclosure
This is an independent, source-backed HeyClaude content entry submitted by
oktofeesh1. It is not sponsored by ShellCheck or the ShellCheck maintainers.
The hook expects a user-installed ShellCheck binary and does not package,
redistribute, or verify a ShellCheck release artifact.