claude-code-bash-patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited claude-code-bash-patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Status: Production Ready ✅ Last Updated: 2025-11-07 Dependencies: Claude Code CLI (latest version) Official Docs: https://docs.claude.com/en/docs/claude-code/tools
The Bash tool is Claude Code's primary interface for executing command-line operations. Unlike specialized tools (Read, Grep, Glob), the Bash tool provides direct shell access for complex workflows.
Key Characteristics:
When to Use Bash Tool:
When NOT to Use Bash Tool:
# Single command
npm install
# Sequential with && (stops on first failure)
npm install && npm run build
# Sequential with ; (continues regardless)
npm install ; npm run build
# Parallel execution (make multiple Bash tool calls)
# Call 1: git status
# Call 2: git diff
# Call 3: git logGolden Rule: Use && when you care about failures, ; when you don't, and parallel calls when operations are independent.
Hooks let you run code before/after tool execution. Here's a simple audit logger:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date -Iseconds)] $(echo \"$CLAUDE_TOOL_INPUT\" | jq -r '.tool_input.command')\" >> ~/.claude/bash-audit.log"
}
]
}
]
}
}Save this to ~/.claude/settings.json to log every bash command with timestamp.
When to Use: Sequential operations where each depends on previous success
Syntax: command1 && command2 && command3
Example: Build and deploy workflow
npm install && npm run build && npx wrangler deployWhy It Matters:
Anti-Pattern: Using ; when you care about failures
# ❌ Wrong: Continues even if install fails
npm install ; npm run build
# ✅ Correct: Stops if install fails
npm install && npm run buildAdvanced: Conditional execution with ||
# Run tests, or echo failure message
npm test || echo "Tests failed, not deploying"
# Try npm ci, fall back to npm install
npm ci || npm installWhen to Use: Independent operations that can run simultaneously
How: Make multiple Bash tool calls in a single message
Example: Git workflow pre-commit analysis
# Claude makes 3 parallel Bash calls in one message:
Call 1: git status
Call 2: git diff --staged
Call 3: git log -5 --onelineBenefits:
Important: Only parallelize truly independent operations. If Call 2 depends on Call 1's output, run sequentially.
When to Use: Git commits, file creation, complex strings with newlines
Syntax: cat <<'EOF' ... EOF
Example: Git commit with detailed message
git commit -m "$(cat <<'EOF'
feat(auth): Add JWT verification middleware
Implement custom JWT template support for Clerk auth.
Extracts email and metadata claims for user context.
🤖 Generated with Claude Code
Co-Authored-By: Claude <[email protected]>
EOF
)"Why Single Quotes: <<'EOF' prevents variable expansion in the content. Use <<"EOF" if you want variables to expand.
Common Mistake: Forgetting quotes around $() wrapper
# ❌ Wrong: Newlines lost
git commit -m $(cat <<'EOF'
Line 1
Line 2
EOF
)
# ✅ Correct: Preserves newlines
git commit -m "$(cat <<'EOF'
Line 1
Line 2
EOF
)"When to Use: Need to process command output before using it
Pattern: Command substitution with $()
Example: Get current branch name
BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "Current branch: $BRANCH"Example: Conditional logic based on output
if git diff --quiet; then
echo "No changes detected"
else
echo "Changes detected, running tests..."
npm test
fiLimitation: Output truncated at 30,000 characters. For large outputs:
# Limit output
npm test 2>&1 | head -100
# Or save to file
npm test > test-results.txt && tail -50 test-results.txtWhen to Use: Different actions based on conditions
Pattern: Test commands with && / ||
Example: Run tests only if files changed
git diff --quiet || npm testExample: Different commands based on file existence
[ -f package-lock.json ] && npm ci || npm installExample: Multi-condition logic
if [ -f pnpm-lock.yaml ]; then
pnpm install
elif [ -f yarn.lock ]; then
yarn install
else
npm install
fiHooks are shell commands or Claude prompts that run before/after tool execution. They're your security guards, cleanup crew, and automation helpers.
Purpose: Runs before tool execution, can block or modify behavior
Exit Codes:
0 = Allow execution1 = Block with generic error2 = Block with custom error message (from stderr)#### Use Case 1: Block Dangerous Commands
File: ~/.claude/hooks/dangerous-command-guard.py
#!/usr/bin/env python3
import json
import sys
import re
# Read hook input from stdin
data = json.load(sys.stdin)
command = data.get('tool_input', {}).get('command', '')
# Dangerous patterns to block
DANGEROUS = [
r'rm\s+-rf\s+/', # Delete root
r'dd\s+if=', # Disk operations
r'mkfs\.', # Format filesystem
r':()\{.*\}:', # Fork bomb
r'sudo\s+rm', # Sudo delete
r'git\s+push.*--force.*main', # Force push to main
]
for pattern in DANGEROUS:
if re.search(pattern, command):
print(f"BLOCKED: Dangerous command pattern '{pattern}'", file=sys.stderr)
sys.exit(2)
# Allow execution
sys.exit(0)Settings Configuration:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/hooks/dangerous-command-guard.py"
}
]
}
]
}
}#### Use Case 2: Log All Bash Commands
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date -Iseconds)] $(echo \"$CLAUDE_TOOL_INPUT\" | jq -r '.tool_input.command')\" >> ~/.claude/bash-audit.log"
}
]
}Result: Every bash command logged with timestamp to ~/.claude/bash-audit.log
#### Use Case 3: Enforce Package Manager
Check for lockfile and block wrong package manager:
#!/bin/bash
# File: ~/.claude/hooks/package-manager-enforcer.sh
COMMAND=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.tool_input.command')
if [ -f pnpm-lock.yaml ] && echo "$COMMAND" | grep -qE '^(npm|yarn) '; then
echo "ERROR: This repo uses pnpm. Please use 'pnpm' instead of 'npm' or 'yarn'." >&2
exit 2
fi
if [ -f yarn.lock ] && echo "$COMMAND" | grep -qE '^(npm|pnpm) '; then
echo "ERROR: This repo uses yarn. Please use 'yarn' instead of 'npm' or 'pnpm'." >&2
exit 2
fi
exit 0Purpose: Runs after successful tool execution (exit code 0 only)
Example: Auto-format after file edits
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "FILE=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r '.tool_input.file_path'); [ -f \"$FILE\" ] && prettier --write \"$FILE\" || true"
}
]
}Example: Run tests after code changes
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "FILE=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r '.tool_input.file_path'); if echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx)$'; then npm test -- \"$FILE\"; fi"
}
]
}Purpose: Runs once at session start, sets up environment
Example: Load project-specific environment
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "[ -f .envrc ] && source .envrc || true; env > $CLAUDE_ENV_FILE"
}
]
}
]
}
}Environment Variables Available in Hooks:
$CLAUDE_TOOL_INPUT - JSON string of tool call$CLAUDE_ENV_FILE - Path to environment file$HOME, $USER, $PWD, etc.)These patterns are used by Anthropic's engineering team for 90%+ of git interactions.
Step 1: Gather context (parallel calls)
# Call 1
git status
# Call 2
git diff --staged
# Call 3
git log -5 --onelineWhy Parallel: Independent operations, faster response, better UX
Step 2: Analyze changes
Step 3: Commit with HEREDOC
git add [files] && git commit -m "$(cat <<'EOF'
feat(auth): Add JWT verification middleware
Implement custom JWT template support for Clerk auth.
Extracts email and metadata claims for user context.
🤖 Generated with Claude Code
Co-Authored-By: Claude <[email protected]>
EOF
)"Step 4: Verify
git statusImportant: If pre-commit hook modifies files:
git log -1 --format='%an %ae'git status (should show "ahead of origin")git add . && git commit --amend --no-editgit add . && git commit -m "Apply pre-commit hook changes"Step 1: Understand branch state (parallel)
# Call 1
git status
# Call 2
git diff main...HEAD
# Call 3
git log main..HEAD --onelineStep 2: Analyze ALL commits (not just latest!)
Step 3: Create PR with gh CLI
gh pr create --title "feat: User Authentication" --body "$(cat <<'EOF'
## Summary
- Implement JWT verification with Clerk
- Add login/logout endpoints
- Update database schema for users table
## Test Plan
- [ ] Verify JWT token validation
- [ ] Test login flow
- [ ] Test logout flow
- [ ] Confirm database migrations work
🤖 Generated with Claude Code
EOF
)"Result: Professional PR with clear summary and test plan
Claude knows gh CLI by default. Common patterns:
# View PR comments
gh api repos/owner/repo/pulls/123/comments
# Check CI status
gh run list --limit 5
# Create issue
gh issue create --title "Bug: Description" --body "Details here"
# Comment on PR
gh pr comment 123 --body "LGTM! 🚀"Prefix with npx for local install:
# Deploy worker
npx wrangler deploy
# Run D1 migrations
npx wrangler d1 migrations apply my-db --remote
# Tail logs
npx wrangler tail
# Execute D1 query
npx wrangler d1 execute my-db --command "SELECT * FROM users LIMIT 5"How to teach Claude about your CLI:
Option 1: Document in CLAUDE.md
## Custom Tools
**mycli**: Internal deployment tool
Usage: `mycli deploy --env production --service api`
Help: `mycli --help` for full options
Config: `.mycli.json` in project rootOption 2: Let Claude discover
When encountering mycli commands, run `mycli --help` first to discover available options.Option 3: Create custom command (.claude/commands/deploy.md)
Deploy using our internal mycli tool:
1. Run `mycli --version` to verify installation
2. Check current environment: `mycli env current`
3. Deploy to staging: `mycli deploy --env staging --service api`
4. Run smoke tests: `mycli test smoke --env staging`
5. If tests pass, deploy to production: `mycli deploy --env production --service api`See bundled script: scripts/dangerous-command-guard.py
Blocks:
rm -rf / (delete root)dd if= (disk operations)mkfs.* (format filesystem):(){ :|:& };: (fork bomb)sudo rm (dangerous sudo deletions)git push --force main (force push to main branch)Installation:
# Copy script
cp scripts/dangerous-command-guard.py ~/.claude/hooks/
chmod +x ~/.claude/hooks/dangerous-command-guard.py
# Configure in ~/.claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/hooks/dangerous-command-guard.py"
}
]
}
]
}
}Prevent modification of production config files:
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "FILE=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r '.tool_input.file_path // empty'); if echo \"$FILE\" | grep -qE '(^|\\/)(production\\.env|\\.env\\.production|prod\\.config)$'; then echo 'ERROR: Cannot modify production config files' >&2; exit 2; fi"
}
]
}Log all bash commands for compliance:
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date -Iseconds)] User: $USER | Command: $(echo \"$CLAUDE_TOOL_INPUT\" | jq -r '.tool_input.command')\" >> ~/.claude/audit.log"
}
]
}View audit log:
tail -50 ~/.claude/audit.logCustom commands are markdown files that expand into prompts when invoked.
File: .claude/commands/deploy-staging.md
Deploy the current branch to staging environment:
1. Verify branch is not main (exit if main)
2. Run full test suite (npm test)
3. Build production bundle (npm run build)
4. Deploy with wrangler to staging (npx wrangler deploy --env staging)
5. Run smoke tests against staging URL
6. Share deployment URL with userUsage:
User: /deploy-staging
Claude: [Executes the workflow from the markdown file]✅ Do:
❌ Don't:
Example Command Library:
/deploy-staging - Deploy to staging/deploy-production - Deploy to production (with safety checks)/run-tests - Run full test suite/check-types - TypeScript type checking/format-code - Format all code files/create-migration - Create database migration1. Use Specialized Tools First
Bash is powerful but inefficient for tasks with dedicated tools:
# ❌ Wrong: Use Bash for file reading
cat file.txt
# ✅ Correct: Use Read tool
Read(file_path="file.txt")# ❌ Wrong: Use Bash for file search
find . -name "*.ts"
# ✅ Correct: Use Glob tool
Glob(pattern="**/*.ts")# ❌ Wrong: Use Bash for content search
grep "pattern" file.txt
# ✅ Correct: Use Grep tool
Grep(pattern="pattern", path="file.txt")Why: Specialized tools are faster, use fewer tokens, and provide better formatting.
2. Quote Paths with Spaces
# ❌ Wrong: Breaks on spaces
cd /Users/name/My Documents
# ✅ Correct: Quoted path
cd "/Users/name/My Documents"3. Use && for Dependencies
# ❌ Wrong: Continues even if install fails
npm install ; npm run build
# ✅ Correct: Stops on failure
npm install && npm run build4. Parallel for Independent Operations
# ❌ Wrong: Sequential when could be parallel
git status && git diff && git log
# ✅ Correct: Make 3 parallel Bash tool calls in one message5. HEREDOC for Multi-line Strings
# ❌ Wrong: Escaping nightmare
git commit -m "line1\nline2\nline3"
# ✅ Correct: HEREDOC
git commit -m "$(cat <<'EOF'
line1
line2
line3
EOF
)"6. Always Provide Description
# ❌ Wrong: No context for user
Bash(command="complex-script.sh")
# ✅ Correct: Clear purpose
Bash(
command="complex-script.sh",
description="Run integration tests on staging database"
)1. Don't Use Interactive Commands
Commands that require user input will hang:
# ❌ Wrong: Will hang waiting for input
vim file.txt
nano file.txt
less output.txt
npm install # If package.json has prompts
# ✅ Correct: Use non-interactive alternatives
# Use Edit/Write tools for file editing
npm install --yes2. Don't Chain with Newlines
# ❌ Wrong: Doesn't work as expected
command1
command2
# ✅ Correct: Use && or ;
command1 && command23. Don't Ignore Exit Codes
# ❌ Wrong: Success message even if command failed
risky-command ; echo "Success!"
# ✅ Correct: Check exit code
risky-command && echo "Success!" || echo "Failed!"4. Don't Use cd Excessively
In agent mode, working directory doesn't persist between calls:
# ❌ Wrong: cd doesn't persist
# Call 1: cd /path/to/project
# Call 2: npm test # Runs in original directory!
# ✅ Correct: Use absolute paths or --prefix
npm test --prefix /path/to/project
# ✅ Or chain in single command
cd /path/to/project && npm test5. Don't Skip Error Handling
# ❌ Wrong: No error handling
./script.sh
./cleanup.sh
# ✅ Correct: Handle failures
./script.sh || { echo "Script failed" >&2; exit 1; }
./cleanup.shThis skill prevents 12 documented issues with sources:
Error: bash: line 1: cygpath: command not found Source: https://github.com/anthropics/claude-code/issues/9883 Why It Happens: Bash tool attempts to use cygpath (Cygwin-only) on MSYS/Git Bash
Prevention:
CLAUDE_CODE_GIT_BASH_PATH if using Git BashError: echo test|grep test returns error instead of "test" Source: https://github.com/anthropics/claude-code/issues/774 Why It Happens: Pipe parsing issues in certain configurations
Prevention:
# ❌ Avoid direct pipes
echo test|grep test
# ✅ Use explicit bash -c
bash -c 'echo test | grep test'
# ✅ Or use specialized tools (Grep tool instead)Error: A hanging Promise was canceled Source: Bash tool default timeout is 2 minutes Why It Happens: Long-running commands without timeout configuration
Prevention:
# For long operations, set timeout explicitly
Bash(
command="npm run build",
timeout=600000, # 10 minutes
description="Build production bundle (may take several minutes)"
)
# Or run in background
Bash(
command="npm run build > build.log 2>&1 &",
description="Start build in background"
)Error: Important output missing from response Source: Bash tool truncates output at 30,000 characters Why It Happens: Large command outputs exceed limit
Prevention:
# ❌ Wrong: Full output may be truncated
npm test
# ✅ Correct: Limit output
npm test 2>&1 | head -100
# ✅ Or save to file
npm test > test-results.txt && tail -50 test-results.txtError: CLI fails with "No suitable shell found" Source: https://github.com/anthropics/claude-code/issues/3461 Why It Happens: Shell detection issues in Git Bash environment
Prevention:
Error: Claude loses ability to run Bash() tool Source: https://github.com/anthropics/claude-code/issues/1888 Why It Happens: Session state corruption, often after overnight idle
Prevention:
/permissions commandrestart: true parameter to reset bash sessionError: Command hangs indefinitely, no output Why It Happens: Command expects interactive input (password, confirmation)
Prevention:
# ❌ Wrong: Will hang
npm install # If package.json has interactive prompts
# ✅ Correct: Non-interactive flags
npm install --yes
# ✅ Correct: Provide input via stdin
echo "yes" | command-that-needs-confirmationError: permission denied or command not found Why It Happens: Script not executable or not in PATH
Prevention:
# Make script executable first
chmod +x script.sh && ./script.sh
# Use full path
/usr/local/bin/mycli deploy
# Or use interpreter directly
python3 script.py
node script.jsError: Variable set in one command not available in next Why It Happens: Agent threads reset environment between calls
Prevention:
# ❌ Wrong: Split across calls
# Call 1: export API_KEY=abc123
# Call 2: curl -H "Authorization: $API_KEY" ... # $API_KEY empty!
# ✅ Correct: Same command
export API_KEY=abc123 && curl -H "Authorization: $API_KEY" ...
# ✅ Or use SessionStart hookError: Pre-commit hook changes files, but commit fails Why It Happens: Hook modifies files after staging
Prevention:
# After commit fails due to hook changes:
# 1. Check if you can amend (not pushed, authored by you)
git log -1 --format='%an %ae'
# 2. If safe, amend
git add . && git commit --amend --no-edit
# 3. If not safe, make new commit
git add . && git commit -m "Apply pre-commit hook changes"Error: Bash(*) or Bash(*:*) doesn't grant access Source: https://github.com/anthropics/claude-code/issues/462 Why It Happens: Syntax mismatch in allowlisting
Prevention:
// ❌ Wrong
{"allowedTools": ["Bash(*)"]}
// ✅ Correct
{"allowedTools": ["Bash"]}
// Or specific patterns
{"allowedTools": ["Bash(git *)", "Bash(npm *)"]}Error: Accidental rm -rf / or force push to main Why It Happens: No guardrails on destructive operations
Prevention: Use PreToolUse hook with dangerous command guard (see Security section)
1. dangerous-command-guard.py Purpose: PreToolUse hook to block dangerous bash patterns Usage: Configure in settings.json (see Security section)
2. bash-audit-logger.sh Purpose: Log all bash commands with timestamps Usage: Configure as PreToolUse hook
3. package-manager-enforcer.sh Purpose: Enforce pnpm/yarn/npm based on lockfile Usage: Configure as PreToolUse hook
1. git-workflows.md Deep dive into git automation patterns, commit message formats, PR creation
2. hooks-examples.md Complete hooks configuration examples for common scenarios
3. cli-tool-integration.md How to integrate custom CLI tools with Claude Code
4. security-best-practices.md Comprehensive security guide for bash automation
5. troubleshooting-guide.md Detailed solutions for all 12 known issues
1. settings.json Complete settings.json with hooks examples
2. dangerous-commands.json List of dangerous patterns to block
3. custom-command-template.md Template for creating .claude/commands/ files
4. github-workflow.yml GitHub Actions integration with Claude Code
5. .envrc.example direnv integration for environment management
Required:
Optional (for specific features):
jq (JSON processing in hooks) - brew install jq / apt install jqgh (GitHub CLI integration) - brew install gh / apt install ghbrew install direnvThis skill is based on real-world usage across:
Measured Impact:
bash --version)brew install jq or apt install jqbrew install gh or apt install ghmkdir -p ~/.claude/hooks~/.claude/hooks/chmod +x ~/.claude/hooks/*.sh~/.claude/settings.json with desired hooks.claude/commands/Questions? Issues?
references/troubleshooting-guide.md for detailed solutionsreferences/hooks-examples.md for configuration examples~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.