"""Bash-environment Claude Code PostToolUse example (Python 3.11+). Install as .claude/hooks/check_changed_file.py in an ESLint-configured project. No --fix, shell interpolation or package install. Exit 2 reports a validation failure to Claude; PostToolUse cannot undo a completed edit. """ from __future__ import annotations import json import os from pathlib import Path import subprocess import sys def check(payload: dict, project_root: Path) -> int: root = project_root.resolve(strict=True) raw_path = payload.get("tool_input", {}).get("file_path") if not isinstance(raw_path, str) or not raw_path: raise ValueError("missing tool_input.file_path") path = Path(raw_path) target = (path if path.is_absolute() else root / path).resolve(strict=True) if not target.is_relative_to(root) or not target.is_file(): raise ValueError("file is outside project or is not a regular file") if target.suffix not in {".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"}: return 0 result = subprocess.run( ["npx", "--no-install", "eslint", "--", str(target)], cwd=root, capture_output=True, text=True, timeout=60, check=False, ) if result.returncode: print(result.stdout + result.stderr, file=sys.stderr) return 2 return 0 def main() -> int: try: raw = sys.stdin.read(1024 * 1024 + 1) if len(raw) > 1024 * 1024: raise ValueError("hook input exceeds budget") return check(json.loads(raw), Path(os.environ["CLAUDE_PROJECT_DIR"])) except (OSError, ValueError, KeyError, TypeError, AttributeError, subprocess.SubprocessError) as exc: print(f"Changed-file validation failed: {exc}", file=sys.stderr) return 2 if __name__ == "__main__": raise SystemExit(main())