
shellcheck-cicd-2025
ShellCheck validation as non-negotiable 2025 workflow practice
ShellCheck validation as non-negotiable 2025 workflow practice
🚨 CRITICAL GUIDELINES
Windows File Path Requirements
MANDATORY: Always Use Backslashes on Windows for File Paths
When using Edit or Write tools on Windows, you MUST use backslashes (\) in file paths, NOT forward slashes (/).
Examples:
- ❌ WRONG:
D:/repos/project/file.tsx - ✅ CORRECT:
D:\repos\project\file.tsx
This applies to:
- Edit tool file_path parameter
- Write tool file_path parameter
- All file operations on Windows systems
Documentation Guidelines
NEVER create new documentation files unless explicitly requested by the user.
- Priority: Update existing README.md files rather than creating new documentation
- Repository cleanliness: Keep repository root clean - only README.md unless user requests otherwise
- Style: Documentation should be concise, direct, and professional - avoid AI-generated tone
- User preference: Only create additional .md files when user specifically asks for documentation
ShellCheck CI/CD Integration (2025)
ShellCheck: Non-Negotiable in 2025
ShellCheck is now considered mandatory in modern bash workflows (2025 best practices):
Latest Version: v0.11.0 (August 2025)
What's New:
- Full Bash 5.3 support (
${| cmd; }andsource -p) - New warnings: SC2327/SC2328 (capture group issues)
- POSIX.1-2024 compliance: SC3013 removed (-ot/-nt/-ef now POSIX standard)
- Enhanced static analysis capabilities
- Improved performance and accuracy
Why Mandatory?
- Catches subtle bugs before production
- Prevents common security vulnerabilities
- Enforces consistent code quality
- Required by most DevOps teams
- Standard in enterprise environments
- Supports latest POSIX.1-2024 standard
Installation
# Ubuntu/Debian
apt-get install shellcheck
# macOS
brew install shellcheck
# Alpine (Docker)
apk add shellcheck
# Windows (WSL/Git Bash)
choco install shellcheck
# Or download binary
wget https://github.com/koalaman/shellcheck/releases/latest/download/shellcheck-stable.linux.x86_64.tar.xz
tar -xf shellcheck-stable.linux.x86_64.tar.xz
sudo cp shellcheck-stable/shellcheck /usr/local/bin/
GitHub Actions Integration
Mandatory Pre-Merge Check
# .github/workflows/shellcheck.yml
name: ShellCheck
on:
pull_request:
paths:
- '**.sh'
- '**Dockerfile'
push:
branches: [main]
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
with:
severity: warning
format: gcc # or: tty, json, checkstyle
scandir: './scripts'
# Fail on any issues
ignore_paths: 'node_modules'
# Block merge on failures
- name: Annotate PR
if: failure()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '⛔ ShellCheck validation failed. Fix issues before merging.'
})
Azure DevOps Integration
# azure-pipelines.yml
trigger:
- main
pr:
- main
stages:
- stage: Validate
jobs:
- job: ShellCheck
pool:
vmImage: 'ubuntu-24.04'
steps:
- script: |
sudo apt-get install -y shellcheck
displayName: 'Install ShellCheck'
- script: |
find . -name "*.sh" -type f | xargs shellcheck --format=gcc --severity=warning
displayName: 'Run ShellCheck'
failOnStderr: true
- task: PublishTestResults@2
condition: always()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/shellcheck-results.xml'
failTaskOnFailedTests: true
Git Hooks (Pre-Commit)
# .git/hooks/pre-commit
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# Find all staged .sh files
mapfile -t STAGED_SH < <(git diff --cached --name-only --diff-filter=ACMR | grep '\.sh$' || true)
if [ ${#STAGED_SH[@]} -eq 0 ]; then
exit 0
fi
echo "Running ShellCheck on staged files..."
# Run ShellCheck
shellcheck --format=gcc --severity=warning "${STAGED_SH[@]}"
if [ $? -ne 0 ]; then
echo "⛔ ShellCheck failed. Fix issues before committing."
exit 1
fi
echo "✅ ShellCheck passed"
exit 0
Install Pre-Commit Hook:
chmod +x .git/hooks/pre-commit
# Or use pre-commit framework
# .pre-commit-config.yaml
repos:
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.0
hooks:
- id: shellcheck
args: ['--severity=warning']
# Install
pip install pre-commit
pre-commit install
VS Code Integration
// .vscode/settings.json
{
"shellcheck.enable": true,
"shellcheck.run": "onType",
"shellcheck.executablePath": "/usr/local/bin/shellcheck",
"shellcheck.exclude": ["SC1090", "SC1091"], // Optional excludes
"shellcheck.customArgs": [
"-x", // Follow source files
"--severity=warning"
]
}
Docker Build Integration
# Dockerfile with ShellCheck validation
FROM alpine:3.19 AS builder
# Install ShellCheck
RUN apk add --no-cache shellcheck bash
# Copy scripts
COPY scripts/ /scripts/
# Validate all scripts before continuing
RUN find /scripts -name "*.sh" -type f -exec shellcheck --severity=warning {} +
# Final stage
FROM alpine:3.19
COPY --from=builder /scripts/ /scripts/
RUN chmod +x /scripts/*.sh
ENTRYPOINT ["/scripts/entrypoint.sh"]
Common ShellCheck Rules (2025)
New in v0.11.0: SC2327/SC2328 - Capture Groups
# ❌ Bad - Capture groups may not work as expected
if [[ "$string" =~ ([0-9]+)\.([0-9]+) ]]; then
echo "$1" # Wrong: $1 is script arg, not capture group
fi
# ✅ Good - Use BASH_REMATCH array
if [[ "$string" =~ ([0-9]+)\.([0-9]+) ]]; then
echo "${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
fi
SC2294: eval Negates Array Benefits (New)
# ❌ Bad - eval defeats array safety
eval "command ${array[@]}"
# ✅ Good - Direct array usage
command "${array[@]}"
SC2295: Quote Expansions Inside ${}
# ❌ Bad
echo "${var-$default}" # $default not quoted
# ✅ Good
echo "${var-"$default"}"
SC2086: Quote Variables
# ❌ Bad
file=$1
cat $file # Fails if filename has spaces
# ✅ Good
file=$1
cat "$file"
SC2046: Quote Command Substitution
# ❌ Bad
for file in $(find . -name "*.txt"); do
echo $file
done
# ✅ Good
find . -name "*.txt" -print0 | while IFS= read -r -d '' file; do
echo "$file"
done
SC2155: Separate Declaration and Assignment
# ❌ Bad
local result=$(command) # Hides command exit code
# ✅ Good
local result
result=$(command)
SC2164: Use cd || exit
# ❌ Bad
cd /some/directory
./script.sh # Runs in wrong dir if cd fails
# ✅ Good
cd /some/directory || exit 1
./script.sh
Google Shell Style Guide (50-Line Limit)
2025 recommendation: Keep scripts under 50 lines:
# ❌ Bad: 500-line monolithic script
#!/usr/bin/env bash
# ... 500 lines of code ...
# ✅ Good: Modular scripts < 50 lines each
# lib/logging.sh (20 lines)
log_info() { echo "[INFO] $*"; }
log_error() { echo "[ERROR] $*" >&2; }
# lib/validation.sh (30 lines)
validate_input() { ... }
check_dependencies() { ... }
# main.sh (40 lines)
source "$(dirname "$0")/lib/logging.sh"
source "$(dirname "$0")/lib/validation.sh"
main() {
validate_input "$@"
check_dependencies
# ... core logic ...
}
main "$@"
Enforce in CI/CD
Fail Build on Issues
# Strict enforcement
- name: ShellCheck (Strict)
run: |
shellcheck --severity=warning scripts/*.sh
# Exit code 1 fails the build
# Advisory only (warnings but don't fail)
- name: ShellCheck (Advisory)
run: |
shellcheck --severity=warning scripts/*.sh || true
# Logs warnings but doesn't fail
Generate Reports
# JSON format for parsing
shellcheck --format=json scripts/*.sh > shellcheck-report.json
# GitHub annotations format
shellcheck --format=gcc scripts/*.sh
# Human-readable
shellcheck --format=tty scripts/*.sh
Modern Error Handling Trio (2025)
Always use with ShellCheck validation:
#!/usr/bin/env bash
# Modern error handling (non-negotiable in 2025)
set -o errexit # Exit on command failure
set -o nounset # Exit on undefined variable
set -o pipefail # Exit on pipe failure
# ShellCheck approved
main() {
local config_file="${1:?Config file required}"
if [[ ! -f "$config_file" ]]; then
echo "Error: Config file not found: $config_file" >&2
return 1
fi
# Safe command execution
local result
result=$(process_config "$config_file")
echo "$result"
}
main "$@"
Best Practices (2025)
- Run ShellCheck in CI/CD (mandatory)
- Use pre-commit hooks to catch issues early
- Keep scripts under 50 lines (Google Style Guide)
- Use modern error handling trio (errexit, nounset, pipefail)
- Fix all warnings before merging
- Document any disabled rules with reasoning
- Integrate with IDE for real-time feedback
Resources
You Might Also Like
Related Skills

fix
Use when you have lint errors, formatting issues, or before committing code to ensure it passes CI.
facebook
frontend-testing
Generate Vitest + React Testing Library tests for Dify frontend components, hooks, and utilities. Triggers on testing, spec files, coverage, Vitest, RTL, unit tests, integration tests, or write/review test requests.
langgenius
frontend-code-review
Trigger when the user requests a review of frontend files (e.g., `.tsx`, `.ts`, `.js`). Support both pending-change reviews and focused file reviews while applying the checklist rules.
langgenius
code-reviewer
Use this skill to review code. It supports both local changes (staged or working tree) and remote Pull Requests (by ID or URL). It focuses on correctness, maintainability, and adherence to project standards.
google-gemini
session-logs
Search and analyze your own session logs (older/parent conversations) using jq.
moltbot
