Loading...
Validates GitHub Actions workflow files for syntax errors and best practices
{
"hookConfig": {
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/github-actions-workflow-validator.sh",
"matchers": [
"write",
"edit"
]
}
}
},
"scriptContent": "#!/usr/bin/env bash\n\n# Read the tool input from stdin\nINPUT=$(cat)\nTOOL_NAME=$(echo \"$INPUT\" | jq -r '.tool_name')\nFILE_PATH=$(echo \"$INPUT\" | jq -r '.tool_input.file_path // .tool_input.path // \"\"')\n\nif [ -z \"$FILE_PATH\" ]; then\n exit 0\nfi\n\n# Check if this is a GitHub Actions workflow file\nif [[ \"$FILE_PATH\" == *.github/workflows/*.yml ]] || [[ \"$FILE_PATH\" == *.github/workflows/*.yaml ]]; then\n echo \"đ Validating GitHub Actions workflow: $(basename \"$FILE_PATH\")\" >&2\n \n # Initialize validation counters\n WARNINGS=0\n ERRORS=0\n SUGGESTIONS=0\n \n # Function to report issues\n report_issue() {\n local level=\"$1\"\n local message=\"$2\"\n \n case \"$level\" in\n \"ERROR\")\n echo \"â ERROR: $message\" >&2\n ERRORS=$((ERRORS + 1))\n ;;\n \"WARNING\")\n echo \"â ī¸ WARNING: $message\" >&2\n WARNINGS=$((WARNINGS + 1))\n ;;\n \"SUGGESTION\")\n echo \"đĄ SUGGESTION: $message\" >&2\n SUGGESTIONS=$((SUGGESTIONS + 1))\n ;;\n \"INFO\")\n echo \"âšī¸ INFO: $message\" >&2\n ;;\n esac\n }\n \n # Check if file exists and is readable\n if [ ! -f \"$FILE_PATH\" ]; then\n report_issue \"ERROR\" \"Workflow file not found: $FILE_PATH\"\n exit 1\n fi\n \n # 1. YAML Syntax Validation\n echo \"đ Checking YAML syntax...\" >&2\n \n # Try actionlint first (most comprehensive)\n if command -v actionlint &> /dev/null; then\n echo \" Using actionlint for comprehensive validation...\" >&2\n \n ACTIONLINT_OUTPUT=$(actionlint \"$FILE_PATH\" 2>&1)\n ACTIONLINT_EXIT_CODE=$?\n \n if [ $ACTIONLINT_EXIT_CODE -eq 0 ]; then\n echo \" â
actionlint validation passed\" >&2\n else\n report_issue \"ERROR\" \"actionlint validation failed\"\n echo \"$ACTIONLINT_OUTPUT\" >&2\n fi\n else\n # Fallback to yamllint\n if command -v yamllint &> /dev/null; then\n echo \" Using yamllint for YAML validation...\" >&2\n if yamllint \"$FILE_PATH\" 2>/dev/null; then\n echo \" â
YAML syntax valid (yamllint)\" >&2\n else\n report_issue \"WARNING\" \"yamllint found issues in YAML syntax\"\n fi\n # Fallback to Python YAML parser\n elif command -v python3 &> /dev/null; then\n echo \" Using Python YAML parser for validation...\" >&2\n if python3 -c \"import yaml; yaml.safe_load(open('$FILE_PATH'))\" 2>/dev/null; then\n echo \" â
YAML syntax valid (Python)\" >&2\n else\n report_issue \"ERROR\" \"Invalid YAML syntax detected\"\n fi\n else\n report_issue \"WARNING\" \"No YAML validator available (actionlint, yamllint, or python3)\"\n fi\n fi\n \n # 2. Action Version Validation\n echo \"đ Checking action versions...\" >&2\n \n # Check for outdated action versions\n OUTDATED_ACTIONS=(\n \"actions/checkout@v[12]\"\n \"actions/setup-node@v[12]\"\n \"actions/setup-python@v[12]\"\n \"actions/cache@v[12]\"\n \"actions/upload-artifact@v[12]\"\n \"actions/download-artifact@v[12]\"\n )\n \n for pattern in \"${OUTDATED_ACTIONS[@]}\"; do\n if grep -q \"$pattern\" \"$FILE_PATH\" 2>/dev/null; then\n ACTION_NAME=$(echo \"$pattern\" | cut -d'@' -f1)\n report_issue \"WARNING\" \"Using outdated $ACTION_NAME version - consider upgrading to latest\"\n fi\n done\n \n # Check for unpinned action versions (security risk)\n if grep -E 'uses:.*@(main|master|develop)' \"$FILE_PATH\" >&2 2>/dev/null; then\n report_issue \"WARNING\" \"Actions using branch references instead of pinned versions detected\"\n echo \" đĄ Use specific version tags or commit SHAs for security\" >&2\n fi\n \n # 3. Security Best Practices\n echo \"đ Checking security best practices...\" >&2\n \n # Check for explicit permissions\n if ! grep -q \"permissions:\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"SUGGESTION\" \"Consider adding explicit 'permissions:' for better security\"\n echo \" Example: permissions: { contents: read, actions: read }\" >&2\n fi\n \n # Check for pull_request_target usage (potential security risk)\n if grep -q \"pull_request_target:\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"WARNING\" \"pull_request_target can be a security risk - ensure proper handling\"\n fi\n \n # Check for secrets in plain text (basic check)\n if grep -iE '(password|secret|token|key).*:.*[a-zA-Z0-9]+' \"$FILE_PATH\" | grep -v '\\${{' >&2 2>/dev/null; then\n report_issue \"ERROR\" \"Potential hardcoded secrets detected - use GitHub secrets instead\"\n fi\n \n # 4. Performance and Best Practices\n echo \"⥠Checking performance best practices...\" >&2\n \n # Check for caching\n if ! grep -q \"cache\" \"$FILE_PATH\" 2>/dev/null && (grep -q \"npm install\" \"$FILE_PATH\" || grep -q \"pip install\" \"$FILE_PATH\" || grep -q \"bundle install\" \"$FILE_PATH\") 2>/dev/null; then\n report_issue \"SUGGESTION\" \"Consider adding caching for dependencies to improve workflow speed\"\n fi\n \n # Check for matrix strategy usage\n if ! grep -q \"strategy:\" \"$FILE_PATH\" 2>/dev/null && grep -q \"runs-on:\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"SUGGESTION\" \"Consider using matrix strategy for testing multiple versions/platforms\"\n fi\n \n # Check for conditional job execution\n if ! grep -q \"if:\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"SUGGESTION\" \"Consider using conditional execution to optimize workflow runs\"\n fi\n \n # 5. Workflow Structure Validation\n echo \"đ Checking workflow structure...\" >&2\n \n # Check for required fields\n if ! grep -q \"name:\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"WARNING\" \"Workflow should have a descriptive 'name' field\"\n fi\n \n if ! grep -q \"on:\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"ERROR\" \"Workflow must have 'on:' trigger definition\"\n fi\n \n if ! grep -q \"jobs:\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"ERROR\" \"Workflow must have 'jobs:' section\"\n fi\n \n # Check for environment variables\n if grep -q \"env:\" \"$FILE_PATH\" 2>/dev/null; then\n echo \" â
Environment variables defined\" >&2\n fi\n \n # 6. Action-Specific Checks\n echo \"đ¯ Checking action-specific patterns...\" >&2\n \n # Check for Node.js setup\n if grep -q \"actions/setup-node\" \"$FILE_PATH\" 2>/dev/null; then\n if ! grep -q \"node-version\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"WARNING\" \"setup-node should specify node-version\"\n fi\n \n if ! grep -q \"cache:\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"SUGGESTION\" \"Consider enabling cache for setup-node (e.g., cache: npm)\"\n fi\n fi\n \n # Check for Python setup\n if grep -q \"actions/setup-python\" \"$FILE_PATH\" 2>/dev/null; then\n if ! grep -q \"python-version\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"WARNING\" \"setup-python should specify python-version\"\n fi\n fi\n \n # Check for checkout action\n if grep -q \"actions/checkout\" \"$FILE_PATH\" 2>/dev/null; then\n # Check if fetch-depth is appropriate for the use case\n if grep -q \"fetch-depth: 0\" \"$FILE_PATH\" 2>/dev/null; then\n report_issue \"INFO\" \"Using full history checkout - ensure this is necessary\"\n fi\n fi\n \n # 7. Generate Summary Report\n echo \"\" >&2\n echo \"đ GitHub Actions Validation Summary:\" >&2\n echo \"=====================================\" >&2\n echo \" đ Workflow: $(basename \"$FILE_PATH\")\" >&2\n echo \" â Errors: $ERRORS\" >&2\n echo \" â ī¸ Warnings: $WARNINGS\" >&2\n echo \" đĄ Suggestions: $SUGGESTIONS\" >&2\n \n if [ \"$ERRORS\" -eq 0 ] && [ \"$WARNINGS\" -eq 0 ]; then\n echo \" â
Validation Status: PASSED\" >&2\n elif [ \"$ERRORS\" -eq 0 ]; then\n echo \" â
Validation Status: PASSED (with warnings)\" >&2\n else\n echo \" â Validation Status: FAILED\" >&2\n fi\n \n echo \"\" >&2\n echo \"đĄ GitHub Actions Best Practices:\" >&2\n echo \" âĸ Pin actions to specific versions or commit SHAs\" >&2\n echo \" âĸ Use explicit permissions for security\" >&2\n echo \" âĸ Cache dependencies to improve performance\" >&2\n echo \" âĸ Use matrix strategies for cross-platform testing\" >&2\n echo \" âĸ Add conditional execution to optimize runs\" >&2\n echo \" âĸ Keep secrets out of workflow files\" >&2\n \n # Exit with error if there are critical issues\n if [ \"$ERRORS\" -gt 0 ]; then\n echo \"â ī¸ Workflow has critical errors that should be fixed\" >&2\n exit 1\n fi\n \nelse\n # Not a GitHub Actions workflow file\n exit 0\nfi\n\nexit 0"
}.claude/hooks/~/.claude/hooks/{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/github-actions-workflow-validator.sh",
"matchers": [
"write",
"edit"
]
}
}
}#!/usr/bin/env bash
# Read the tool input from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Check if this is a GitHub Actions workflow file
if [[ "$FILE_PATH" == *.github/workflows/*.yml ]] || [[ "$FILE_PATH" == *.github/workflows/*.yaml ]]; then
echo "đ Validating GitHub Actions workflow: $(basename "$FILE_PATH")" >&2
# Initialize validation counters
WARNINGS=0
ERRORS=0
SUGGESTIONS=0
# Function to report issues
report_issue() {
local level="$1"
local message="$2"
case "$level" in
"ERROR")
echo "â ERROR: $message" >&2
ERRORS=$((ERRORS + 1))
;;
"WARNING")
echo "â ī¸ WARNING: $message" >&2
WARNINGS=$((WARNINGS + 1))
;;
"SUGGESTION")
echo "đĄ SUGGESTION: $message" >&2
SUGGESTIONS=$((SUGGESTIONS + 1))
;;
"INFO")
echo "âšī¸ INFO: $message" >&2
;;
esac
}
# Check if file exists and is readable
if [ ! -f "$FILE_PATH" ]; then
report_issue "ERROR" "Workflow file not found: $FILE_PATH"
exit 1
fi
# 1. YAML Syntax Validation
echo "đ Checking YAML syntax..." >&2
# Try actionlint first (most comprehensive)
if command -v actionlint &> /dev/null; then
echo " Using actionlint for comprehensive validation..." >&2
ACTIONLINT_OUTPUT=$(actionlint "$FILE_PATH" 2>&1)
ACTIONLINT_EXIT_CODE=$?
if [ $ACTIONLINT_EXIT_CODE -eq 0 ]; then
echo " â
actionlint validation passed" >&2
else
report_issue "ERROR" "actionlint validation failed"
echo "$ACTIONLINT_OUTPUT" >&2
fi
else
# Fallback to yamllint
if command -v yamllint &> /dev/null; then
echo " Using yamllint for YAML validation..." >&2
if yamllint "$FILE_PATH" 2>/dev/null; then
echo " â
YAML syntax valid (yamllint)" >&2
else
report_issue "WARNING" "yamllint found issues in YAML syntax"
fi
# Fallback to Python YAML parser
elif command -v python3 &> /dev/null; then
echo " Using Python YAML parser for validation..." >&2
if python3 -c "import yaml; yaml.safe_load(open('$FILE_PATH'))" 2>/dev/null; then
echo " â
YAML syntax valid (Python)" >&2
else
report_issue "ERROR" "Invalid YAML syntax detected"
fi
else
report_issue "WARNING" "No YAML validator available (actionlint, yamllint, or python3)"
fi
fi
# 2. Action Version Validation
echo "đ Checking action versions..." >&2
# Check for outdated action versions
OUTDATED_ACTIONS=(
"actions/checkout@v[12]"
"actions/setup-node@v[12]"
"actions/setup-python@v[12]"
"actions/cache@v[12]"
"actions/upload-artifact@v[12]"
"actions/download-artifact@v[12]"
)
for pattern in "${OUTDATED_ACTIONS[@]}"; do
if grep -q "$pattern" "$FILE_PATH" 2>/dev/null; then
ACTION_NAME=$(echo "$pattern" | cut -d'@' -f1)
report_issue "WARNING" "Using outdated $ACTION_NAME version - consider upgrading to latest"
fi
done
# Check for unpinned action versions (security risk)
if grep -E 'uses:.*@(main|master|develop)' "$FILE_PATH" >&2 2>/dev/null; then
report_issue "WARNING" "Actions using branch references instead of pinned versions detected"
echo " đĄ Use specific version tags or commit SHAs for security" >&2
fi
# 3. Security Best Practices
echo "đ Checking security best practices..." >&2
# Check for explicit permissions
if ! grep -q "permissions:" "$FILE_PATH" 2>/dev/null; then
report_issue "SUGGESTION" "Consider adding explicit 'permissions:' for better security"
echo " Example: permissions: { contents: read, actions: read }" >&2
fi
# Check for pull_request_target usage (potential security risk)
if grep -q "pull_request_target:" "$FILE_PATH" 2>/dev/null; then
report_issue "WARNING" "pull_request_target can be a security risk - ensure proper handling"
fi
# Check for secrets in plain text (basic check)
if grep -iE '(password|secret|token|key).*:.*[a-zA-Z0-9]+' "$FILE_PATH" | grep -v '\${{' >&2 2>/dev/null; then
report_issue "ERROR" "Potential hardcoded secrets detected - use GitHub secrets instead"
fi
# 4. Performance and Best Practices
echo "⥠Checking performance best practices..." >&2
# Check for caching
if ! grep -q "cache" "$FILE_PATH" 2>/dev/null && (grep -q "npm install" "$FILE_PATH" || grep -q "pip install" "$FILE_PATH" || grep -q "bundle install" "$FILE_PATH") 2>/dev/null; then
report_issue "SUGGESTION" "Consider adding caching for dependencies to improve workflow speed"
fi
# Check for matrix strategy usage
if ! grep -q "strategy:" "$FILE_PATH" 2>/dev/null && grep -q "runs-on:" "$FILE_PATH" 2>/dev/null; then
report_issue "SUGGESTION" "Consider using matrix strategy for testing multiple versions/platforms"
fi
# Check for conditional job execution
if ! grep -q "if:" "$FILE_PATH" 2>/dev/null; then
report_issue "SUGGESTION" "Consider using conditional execution to optimize workflow runs"
fi
# 5. Workflow Structure Validation
echo "đ Checking workflow structure..." >&2
# Check for required fields
if ! grep -q "name:" "$FILE_PATH" 2>/dev/null; then
report_issue "WARNING" "Workflow should have a descriptive 'name' field"
fi
if ! grep -q "on:" "$FILE_PATH" 2>/dev/null; then
report_issue "ERROR" "Workflow must have 'on:' trigger definition"
fi
if ! grep -q "jobs:" "$FILE_PATH" 2>/dev/null; then
report_issue "ERROR" "Workflow must have 'jobs:' section"
fi
# Check for environment variables
if grep -q "env:" "$FILE_PATH" 2>/dev/null; then
echo " â
Environment variables defined" >&2
fi
# 6. Action-Specific Checks
echo "đ¯ Checking action-specific patterns..." >&2
# Check for Node.js setup
if grep -q "actions/setup-node" "$FILE_PATH" 2>/dev/null; then
if ! grep -q "node-version" "$FILE_PATH" 2>/dev/null; then
report_issue "WARNING" "setup-node should specify node-version"
fi
if ! grep -q "cache:" "$FILE_PATH" 2>/dev/null; then
report_issue "SUGGESTION" "Consider enabling cache for setup-node (e.g., cache: npm)"
fi
fi
# Check for Python setup
if grep -q "actions/setup-python" "$FILE_PATH" 2>/dev/null; then
if ! grep -q "python-version" "$FILE_PATH" 2>/dev/null; then
report_issue "WARNING" "setup-python should specify python-version"
fi
fi
# Check for checkout action
if grep -q "actions/checkout" "$FILE_PATH" 2>/dev/null; then
# Check if fetch-depth is appropriate for the use case
if grep -q "fetch-depth: 0" "$FILE_PATH" 2>/dev/null; then
report_issue "INFO" "Using full history checkout - ensure this is necessary"
fi
fi
# 7. Generate Summary Report
echo "" >&2
echo "đ GitHub Actions Validation Summary:" >&2
echo "=====================================" >&2
echo " đ Workflow: $(basename "$FILE_PATH")" >&2
echo " â Errors: $ERRORS" >&2
echo " â ī¸ Warnings: $WARNINGS" >&2
echo " đĄ Suggestions: $SUGGESTIONS" >&2
if [ "$ERRORS" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then
echo " â
Validation Status: PASSED" >&2
elif [ "$ERRORS" -eq 0 ]; then
echo " â
Validation Status: PASSED (with warnings)" >&2
else
echo " â Validation Status: FAILED" >&2
fi
echo "" >&2
echo "đĄ GitHub Actions Best Practices:" >&2
echo " âĸ Pin actions to specific versions or commit SHAs" >&2
echo " âĸ Use explicit permissions for security" >&2
echo " âĸ Cache dependencies to improve performance" >&2
echo " âĸ Use matrix strategies for cross-platform testing" >&2
echo " âĸ Add conditional execution to optimize runs" >&2
echo " âĸ Keep secrets out of workflow files" >&2
# Exit with error if there are critical issues
if [ "$ERRORS" -gt 0 ]; then
echo "â ī¸ Workflow has critical errors that should be fixed" >&2
exit 1
fi
else
# Not a GitHub Actions workflow file
exit 0
fi
exit 0actionlint not found but validation still runs
Hook falls back to yamllint or Python YAML parser automatically. Install actionlint with brew install actionlint on macOS or download from GitHub releases for comprehensive validation.
False positives for deprecated actions warning
Update the OUTDATED_ACTIONS array in the hook script to exclude specific action patterns. Use version pinning with commit SHAs instead of version tags to avoid warnings.
Hook fails on pull_request_target workflows
This is a security warning, not failure. Review pull_request_target usage carefully and ensure proper checkout action configuration with explicit ref parameter for untrusted code.
YAML validation passes but workflow fails in GitHub
Install actionlint for GitHub-specific validation beyond YAML syntax. Check for GitHub Actions context variables, expression syntax, and action version compatibility issues.
Performance suggestions appear for optimized workflows
Suggestions are recommendations, not errors. Customize the hook script to skip specific checks by commenting out sections or adjust thresholds in the performance validation logic.
Loading reviews...
Join our community of Claude power users. No spam, unsubscribe anytime.
Automated accessibility testing and compliance checking for web applications following WCAG guidelines
Automatically generates or updates API documentation when endpoint files are modified
Automatically formats code files after Claude writes or edits them using Prettier, Black, or other formatters