# Lifecycle Hooks
Run custom scripts at test lifecycle events.
## Overview
Hooks are executable scripts in `.checkmate/hooks/`:
```
.checkmate/
└── hooks/
├── pre_run # Before tests start
├── post_run # After tests complete
├── on_pass # All tests passed
└── on_fail # Any tests failed
```
## Hook Events
| `pre_run` | Before tests start | Setup, notifications |
| `post_run` | After tests complete (always) | Cleanup, reporting |
| `on_pass` | All tests passed | Success notifications |
| `on_fail` | Any tests failed | Alerts, issue creation |
### Execution Order
1. `pre_run` (before)
2. Tests execute
3. `post_run` (always)
4. `on_pass` OR `on_fail` (based on results)
## Creating Hooks
### 1. Create the Script
```bash
# Create hook
cat > .checkmate/hooks/on_fail << 'EOF'
#!/bin/bash
echo "Tests failed!"
echo "Run: $CM_RUN_ID"
echo "Failed: $CM_FAILED / $CM_TOTAL"
EOF
# Make executable
chmod +x .checkmate/hooks/on_fail
```
### 2. Supported Formats
Hooks can be:
- Executable scripts (with shebang): `pre_run`
- Shell scripts: `pre_run.sh`, `pre_run.bash`
- Python scripts: `pre_run.py`
- Ruby scripts: `pre_run.rb`
- Node scripts: `pre_run.js`
## Environment Variables
Hooks receive context via environment:
| `CM_RUN_ID` | Run identifier | `cm-run-a3f` |
| `CM_SPEC` | Spec file name | `users.yaml` |
| `CM_TOTAL` | Total test count | `5` |
| `CM_PASSED` | Passed count | `4` |
| `CM_FAILED` | Failed count | `1` |
| `CM_ERRORS` | Error count | `0` |
| `CM_DURATION_MS` | Duration (ms) | `1234` |
### Availability by Hook
| `CM_SPEC` | ✓ | ✓ | ✓ | ✓ |
| `CM_RUN_ID` | - | ✓ | ✓ | ✓ |
| `CM_TOTAL` | - | ✓ | ✓ | ✓ |
| `CM_PASSED` | - | ✓ | ✓ | ✓ |
| `CM_FAILED` | - | ✓ | ✓ | ✓ |
| `CM_ERRORS` | - | ✓ | ✓ | ✓ |
| `CM_DURATION_MS` | - | ✓ | ✓ | ✓ |
## Examples
### Slack Notification on Failure
`.checkmate/hooks/on_fail`:
```bash
#!/bin/bash
curl -X POST "$SLACK_WEBHOOK" \
-H 'Content-Type: application/json' \
-d "{
\"text\": \"API Tests Failed\",
\"attachments\": [{
\"color\": \"danger\",
\"fields\": [
{\"title\": \"Run\", \"value\": \"$CM_RUN_ID\", \"short\": true},
{\"title\": \"Spec\", \"value\": \"$CM_SPEC\", \"short\": true},
{\"title\": \"Results\", \"value\": \"$CM_PASSED/$CM_TOTAL passed\", \"short\": true},
{\"title\": \"Duration\", \"value\": \"${CM_DURATION_MS}ms\", \"short\": true}
]
}]
}"
```
### Create GitHub Issue on Failure
`.checkmate/hooks/on_fail`:
```bash
#!/bin/bash
gh issue create \
--title "API Test Failure: $CM_SPEC" \
--body "Run: $CM_RUN_ID
Failed: $CM_FAILED / $CM_TOTAL tests
Duration: ${CM_DURATION_MS}ms
See \`cm show $CM_RUN_ID\` for details."
```
### Log to File
`.checkmate/hooks/post_run`:
```bash
#!/bin/bash
echo "$(date -Iseconds) $CM_RUN_ID $CM_SPEC $CM_PASSED/$CM_TOTAL" >> /var/log/checkmate.log
```
### Sound Alert (macOS)
`.checkmate/hooks/on_fail`:
```bash
#!/bin/bash
say "Tests failed. $CM_FAILED of $CM_TOTAL tests failed."
```
`.checkmate/hooks/on_pass`:
```bash
#!/bin/bash
say "All $CM_TOTAL tests passed."
```
### Python Hook
`.checkmate/hooks/on_fail.py`:
```python
#!/usr/bin/env python3
import os
import requests
run_id = os.environ.get('CM_RUN_ID')
failed = os.environ.get('CM_FAILED')
total = os.environ.get('CM_TOTAL')
# Send to monitoring service
requests.post('https://monitoring.example.com/alerts', json={
'type': 'test_failure',
'run_id': run_id,
'failed': int(failed),
'total': int(total)
})
```
### Setup/Teardown
`.checkmate/hooks/pre_run`:
```bash
#!/bin/bash
# Start test database
docker-compose up -d test-db
sleep 5
```
`.checkmate/hooks/post_run`:
```bash
#!/bin/bash
# Stop test database
docker-compose down test-db
```
## Execution Details
### Fire-and-Forget
Hooks run asynchronously (fire-and-forget):
- Test execution doesn't wait for hooks to complete
- Hook failures don't affect test results
- Hooks run in background
### Working Directory
Hooks run with working directory set to project root (parent of `.checkmate/`).
### Error Handling
Hook errors are reported as warnings but don't fail tests:
```
Warning: Failed to run hook on_fail: Permission denied
```
## Best Practices
### 1. Keep Hooks Fast
Hooks shouldn't slow down test execution:
```bash
# Good: Send async
curl -X POST "$WEBHOOK" &
# Avoid: Slow synchronous operation
sleep 10
```
### 2. Handle Missing Variables
Variables may not be set for all hooks:
```bash
#!/bin/bash
if [ -n "$CM_RUN_ID" ]; then
echo "Run: $CM_RUN_ID"
fi
```
### 3. Use Absolute Paths
Working directory is project root:
```bash
#!/bin/bash
LOG_FILE="$HOME/.checkmate/test.log"
echo "$CM_RUN_ID" >> "$LOG_FILE"
```
### 4. Test Hooks Manually
```bash
# Simulate hook environment
CM_RUN_ID="test" CM_FAILED=1 CM_TOTAL=5 ./.checkmate/hooks/on_fail
```
## Troubleshooting
### Hook Not Running
1. Check file exists: `ls -la .checkmate/hooks/`
2. Check executable: `chmod +x .checkmate/hooks/on_fail`
3. Check shebang: First line should be `#!/bin/bash` or similar
4. Run manually to test
### Permission Denied
```bash
chmod +x .checkmate/hooks/on_fail
```
### Script Errors
Test hook manually:
```bash
CM_RUN_ID="test" ./.checkmate/hooks/on_fail
```
---
## See Also
- [CLI Reference](CLI_REFERENCE.md) - Running tests
- [History](HISTORY.md) - Tracking results