home/dot_claude/skills/hooks-cross-platform/SKILL.md
# Cross-Platform Hooks and Status Scripts Guidelines for writing Claude Code hooks and status scripts that work on Windows (PowerShell, Git Bash), WSL, and Linux. ## Activation Activate when: - Creating or modifying files in `.claude/hooks/` - Creating or modifying `.claude/claude-status` - Writing shell scripts that will run in Claude Code context - Debugging hook execution failures ## Critical Rules ### 1. Shebang Lines **Bash scripts**: Always use `#!/usr/bin/env bash` (NOT `#!/bin/bash
npx skillsauth add salverius-tech/dotfiles home/dot_claude/skills/hooks-cross-platformInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Guidelines for writing Claude Code hooks and status scripts that work on Windows (PowerShell, Git Bash), WSL, and Linux.
Activate when:
.claude/hooks/.claude/claude-statusBash scripts: Always use #!/usr/bin/env bash (NOT #!/bin/bash)
#!/usr/bin/env bash
# This finds bash in PATH - works on all platforms
Python scripts: Use #!/usr/bin/env python (NOT python3)
#!/usr/bin/env python
# Windows often has 'python', not 'python3' in PATH
Scripts MUST use LF (Unix) line endings, not CRLF (Windows).
Enforce in .gitattributes:
*.sh text eol=lf
*.py text eol=lf
.claude/claude-status text eol=lf
.claude/hooks/* text eol=lf
Symptoms of CRLF issues:
\r': command not foundbad interpreter: No such file or directory# Cross-platform: HOME on Unix, USERPROFILE on Windows
USER_HOME="${HOME:-$USERPROFILE}"
import os
home = os.path.expanduser('~') # Works everywhere
Always check for tools before using, with fallback:
# jq-first with Python fallback
if command -v jq &>/dev/null; then
result=$(echo "$json" | jq -r '.field')
else
# Python fallback (guaranteed available)
result=$(echo "$json" | python -c "
import json, sys
data = json.load(sys.stdin)
print(data.get('field', ''))
")
fi
is_wsl() {
[[ -n "$WSL_DISTRO_NAME" || -f /proc/sys/fs/binfmt_misc/WSLInterop ]]
}
When running in WSL but receiving Windows paths from JSON:
# Convert C:/Users/... or C:\Users\... to /mnt/c/Users/...
to_wsl_path() {
local p="$1"
p="${p//\\//}" # Backslash to forward slash
if [[ "$p" =~ ^([A-Za-z]):/(.*) ]]; then
local drive="${BASH_REMATCH[1],,}" # lowercase
local rest="${BASH_REMATCH[2]}"
echo "/mnt/$drive/$rest"
else
echo "$p"
fi
}
Strip drive letters and mount points for consistent display:
normalize_path() {
local p="$1"
p="${p//\\//}" # Backslash to forward slash
p="${p#[A-Za-z]:}" # Remove C:
p="${p#/[a-z]/}" # Remove /c/
p="${p#/mnt/[a-z]/}" # Remove /mnt/c/
echo "$p"
}
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
# Windows Git Bash / MSYS2
WIN_HOME=$(cygpath -w "$USER_HOME" 2>/dev/null || echo "$USERPROFILE")
elif [[ -n "$USERPROFILE" ]]; then
# Windows without cygpath
echo "Windows environment"
else
# Unix (Linux/macOS)
echo "Unix environment"
fi
Hooks receive input via stdin and output via stdout. Use JSON for data exchange to avoid quoting issues:
#!/usr/bin/env python
import json
import sys
# Read input
data = json.load(sys.stdin)
prompt = data.get('prompt', '')
cwd = data.get('cwd', '')
# Do work...
# Output result
output = {
"hookSpecificOutput": {
"additionalContext": "injected context here"
}
}
print(json.dumps(output))
Handle spaces in paths by using tab delimiter:
# Use tab as delimiter to handle spaces in values
IFS=$'\t' read -r var1 var2 <<< $(python -c "
import json, sys
data = json.load(sys.stdin)
print(data.get('field1', '') + '\t' + data.get('field2', ''))
")
Hooks should fail silently to avoid breaking Claude Code:
try:
# Hook logic
print(json.dumps(result))
except Exception as e:
# Log to stderr, return empty success
print(json.dumps({"error": str(e)}), file=sys.stderr)
print(json.dumps({}))
# Exit gracefully on errors
some_command 2>/dev/null || exit 0
{
"hooks": [
{
"name": "my-hook",
"event": "UserPromptSubmit",
"script": "hooks/my-hook.py",
"description": "What this hook does",
"matchers": [
{
"field": "prompt",
"regex": "^/mycommand"
}
]
}
]
}
Available events:
UserPromptSubmit - Before prompt is processedSessionStart - When Claude Code session startsPostToolUse - After a tool is executed#!/usr/bin/env bash
# Cross-platform hook example
set -e
# Cross-platform home
USER_HOME="${HOME:-$USERPROFILE}"
# Read JSON from stdin
stdin_input=$(cat)
# Parse with jq or Python fallback
if command -v jq &>/dev/null; then
cwd=$(echo "$stdin_input" | jq -r '.cwd // ""')
else
cwd=$(echo "$stdin_input" | python -c "
import json, sys
data = json.load(sys.stdin)
print(data.get('cwd', ''))
")
fi
# WSL path conversion if needed
is_wsl() {
[[ -n "$WSL_DISTRO_NAME" || -f /proc/sys/fs/binfmt_misc/WSLInterop ]]
}
if is_wsl; then
cwd="${cwd//\\//}"
if [[ "$cwd" =~ ^([A-Za-z]):/(.*) ]]; then
cwd="/mnt/${BASH_REMATCH[1],,}/${BASH_REMATCH[2]}"
fi
fi
# Do work with $cwd...
# Output JSON result
echo '{"hookSpecificOutput": {"additionalContext": "processed"}}'
#!/usr/bin/env python
"""Cross-platform hook example."""
import json
import sys
import os
from pathlib import Path
def main():
try:
# Read input
data = json.load(sys.stdin)
cwd = Path(data.get('cwd', os.getcwd()))
# pathlib handles cross-platform paths
config_file = cwd / '.config' / 'settings.json'
# os.path.expanduser works everywhere
home = Path(os.path.expanduser('~'))
# Do work...
result = {"hookSpecificOutput": {"additionalContext": "done"}}
print(json.dumps(result))
except Exception as e:
print(json.dumps({"error": str(e)}), file=sys.stderr)
print(json.dumps({}))
if __name__ == "__main__":
main()
~/.claude/debug/ for error logsecho '{"cwd":"/tmp"}' | ./hooks/my-hook.pyfile hooks/my-hook.sh (should show "ASCII text", not "with CRLF")head -1 hooks/my-hook.shls -la hooks/ (scripts need execute bit on Unix)| Issue | Symptom | Fix |
|-------|---------|-----|
| #!/bin/bash shebang | "bad interpreter" on some systems | Use #!/usr/bin/env bash |
| python3 shebang | "python3 not found" on Windows | Use python not python3 |
| CRLF line endings | \r': command not found | Ensure LF endings, add .gitattributes |
| Hardcoded /home/user | Path not found | Use ${HOME:-$USERPROFILE} or os.path.expanduser('~') |
| Windows paths in WSL | Git/file operations fail | Convert C:/ to /mnt/c/ |
| Spaces in paths | Arguments split incorrectly | Use tab delimiter or JSON |
| Missing tool (jq) | Script fails | Always provide Python fallback |
testing
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
testing
Guidelines for optimizing Claude rulesets and instruction files (CLAUDE.md, settings.json) using context efficiency principles. Includes strategies for skill extraction, progressive disclosure, token savings calculation, and deduplication. Manually invoke when optimizing rulesets, reducing context size, extracting content to skills, or improving ruleset organization.
tools
Activate when user needs multi-URL scraping, browser automation pipelines, or efficient tool orchestration to reduce API round-trips and context usage.
development
Claude Code AI-assisted development workflow. Activate when discussing Claude Code usage, AI-assisted coding, prompting strategies, or Claude Code-specific patterns.