← Back to libraryQuestion 235 of 273
🔧CI/CDIntermediate

Shift-Left: Pre-commit Hooks, Linting & Static Analysis

📌 Definition:

Shifting left means catching defects as early as possible in the development flow — before code even reaches CI. Pre-commit hooks run fast checks (formatting, linting, type-checking, unit tests on changed files, secret scanning) locally at commit time, and static analysis inspects code for bugs and vulnerabilities without executing it.

📖 Detailed Explanation:

The earlier a defect is caught, the cheaper it is to fix; a lint error caught at commit costs seconds, the same issue caught in a CI run costs minutes of feedback loop, and in production it can cost far more. Pre-commit hooks (via tools like Husky/lint-staged, or pre-commit for Python) run a curated set of fast checks against staged files before the commit is created, so obvious problems never enter history. Static analysis — linters, type checkers, and security/SAST scanners — reads the code to find likely bugs, code smells, and vulnerable patterns without running it, complementing tests (which verify behavior) by catching issues tests might miss. The balance is speed: pre-commit checks must be fast (seconds) or developers bypass them, so heavier analysis runs in CI while only the quickest, highest-value checks run locally. Crucially, local hooks are a convenience, not a guarantee (they can be skipped), so the same checks must also run as CI gates.

🔑 Key Points:
  • Earlier detection = cheaper fixes; shift checks left toward commit time
  • Pre-commit hooks run fast checks on staged files: format, lint, type-check, secret scan
  • Static analysis finds bugs/vulnerabilities by reading code, without executing it
  • Keep local hooks fast (seconds) or they get bypassed; heavy analysis runs in CI
  • Local hooks are skippable — enforce the same checks as CI gates too
🌍 Real-World Example:

A team adds a pre-commit hook running Prettier, ESLint and a secret scanner on staged files. Formatting debates in code review vanish, obvious lint errors never reach CI, and an accidental API key is blocked at commit time — while CI re-runs the same checks so a developer who skips the hook is still caught.

📎 Code Example:
# package.json with Husky + lint-staged (runs on staged files at commit)
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": ["eslint --max-warnings=0", "prettier --write"],
    "*": "gitleaks protect --staged"        // secret scan before commit
  }
}
# .husky/pre-commit
#   npx lint-staged

# CI re-enforces the SAME checks (hooks are skippable with --no-verify):
#   - run: npm run lint && npm run type-check
#   - run: npx gitleaks detect --source .
🎯 Scenario-Based Interview Question:

Scenario: A team relies entirely on pre-commit hooks for linting and secret scanning 'so CI can be lighter'. A contractor commits with --no-verify, bypassing the hooks, and pushes code with lint errors and a leaked token. How do you prevent this while keeping fast local feedback?