Hooks
Guide to Git hooks (Lefthook) and Claude Code hooks configured in Roxabi Boilerplate
Overview
Roxabi Boilerplate uses two layers of hooks: Lefthook for Git hooks (commit-msg, pre-commit, pre-push) and Claude Code hooks for in-editor automation during development.
Git Hooks (Lefthook)
Lefthook manages Git hooks to enforce code quality at three stages of the Git workflow. Configuration lives in lefthook.yml at the repository root.
Hook Stages
| Stage | Hook | What It Does |
|---|---|---|
commit-msg | commitlint | Validates commit messages against the Conventional Commits format |
pre-commit | biome | Runs biome check --write on staged JS/TS/JSON files, auto-fixes issues, and re-stages the corrected files |
pre-push | lint | Runs bun run lint (Biome lint on the full repo) |
pre-push | typecheck | Runs bun run typecheck (TypeScript strict mode) |
pre-push | test | Runs bun run test:coverage (full test suite with coverage) |
pre-push | i18n | Runs bun run i18n:check (translation completeness) |
pre-push | license | Runs bun run license:check (dependency license compliance) |
The pre-commit and pre-push stages run their commands in parallel for faster execution.
Installation
Lefthook installs automatically when you run bun install thanks to the prepare script in package.json. To install manually:
bunx lefthook installSkipping Hooks
To bypass hooks for a single operation:
git commit --no-verify
git push --no-verifyWarning: --no-verify skips all hooks for that operation. Use sparingly -- these checks exist to prevent broken code from reaching the remote.
Pre-commit Auto-fix Behavior
The pre-commit Biome hook runs with --write and stage_fixed: true. This means if Biome auto-fixes formatting issues in your staged files, the corrected versions are automatically re-staged. You do not need to manually git add after a formatting fix.
Claude Code Hooks
Hooks configured in .claude/settings.json automate common tasks during Claude Code development sessions.
Biome Auto-Format
Trigger: After Edit or Write operations on TS/JS files
Action: Runs biome check --write to format the file
{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "bunx biome check --write \"$CLAUDE_FILE_PATHS\""
}]
}Note: The simplified command above illustrates the hook intent. The actual command in
.claude/settings.jsonadds acasefile-type guard (to restrict which extensions trigger Biome),timeout 10s(to prevent hangs on large files), and2>/dev/null || true(to suppress errors and ensure the hook never blocks the tool operation).
Bun Test Blocker
Trigger: Before any Bash tool invocation (PreToolUse)
Action: Blocks any command that calls bun test without run and redirects to bun run test
Plain bun test invokes Bun's native test runner, which causes a CPU spin in this project due to the Lefthook hook setup. The correct command is bun run test, which delegates to Vitest via TurboRepo — the project's configured test runner. This hook prevents accidental use of the wrong runner during Claude Code development sessions.
Security Check
Trigger: Before Edit or Write operations
Action: Scans for security anti-patterns and warns once per file/rule
{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": ".claude/hooks/security-check.js"
}]
}Security Patterns Detected
| Pattern | Description |
|---|---|
| GitHub Actions Injection | Untrusted input in workflow expressions |
| Dynamic Code Execution | eval() or new Function() |
| XSS Vectors | innerHTML or dangerouslySetInnerHTML |
| Hardcoded Secrets | API keys, passwords in code |
| SQL Injection | Template literal interpolation in queries |
| Command Injection | Template literals in exec/spawn |
Behavior
- Warnings appear once per file per rule per session
- Warnings don't block operations
- State is persisted in
~/.claude-security-warnings.json
Adding Custom Hooks
- Create hook script in
.claude/hooks/ - Make it executable:
chmod +x script.js - Add to
.claude/settings.json
Hook Script Format
#!/usr/bin/env node
const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}')
// Your logic here
// Output (optional)
console.log(JSON.stringify({
decision: 'allow', // or 'block'
message: 'Optional message to display'
}))Environment Variables
Available in hooks:
| Variable | Description |
|---|---|
CLAUDE_TOOL_INPUT | JSON with tool parameters |
CLAUDE_FILE_PATHS | Affected file path(s) |