A simple Docker-based automation service that receives Bitbucket pull request webhooks, clones/validates the repository, and processes them using Claude CLI (not the API).
- 🔗 Receives Bitbucket PR creation webhooks
- 🔒 Webhook signature validation & workspace restriction (yourworkspace)
- 📦 Automatically clones repositories if not already present
- 🔄 Updates existing repositories before processing
- 🤖 Processes PR data with Claude CLI (
--dangerously-skip-permissions) - 🐳 Fully containerized with Docker
- ⚡ Express.js REST API
- 📝 Easy configuration with environment variables
- 📊 Prometheus metrics integration for monitoring
- Docker and Docker Compose installed
- Bitbucket repository with webhook access
- Bitbucket credentials (App Password token and username)
Note: This uses Claude CLI (installed globally in Docker), not the Anthropic API, so you don't need an API key!
📖 For complete setup instructions, see SETUP_GUIDE.md
# Interactive setup (recommended)
npm run setup
# Or start manually after configuration
docker-compose up -d- ✅ Docker and Docker Compose installed
- ✅ Bitbucket repository with webhook access
- ✅ Claude CLI installed globally:
npm install -g @anthropic-ai/claude-code
- Go to your Bitbucket repository settings
- Navigate to Webhooks section
- Click Add webhook
- Configure:
- Title: PR Automation
- URL:
http://your-server:3000/webhook/bitbucket/pr - Status: Active
- Triggers: Select "Pull Request" → "Created"
- Save the webhook
- Webhook Received: Bitbucket sends a webhook when a PR is created
- Project Validation: The system checks if the repository is cloned in
/app/projects- If not cloned: Clones the repository from Bitbucket
- If already exists: Updates the repository (git pull)
- Claude CLI Processing: Executes
claude --dangerously-skip-permissionswith the prompt- Runs in the project directory with terminal access
- Can execute git commands, read files, analyze code
- Outputs text-based review
- Response: Claude's analysis is logged (can be extended to post comments, etc.)
This implementation uses Claude CLI instead of the Anthropic API:
| Feature | Claude CLI | Anthropic API |
|---|---|---|
| Authentication | Uses CLI session (no API key needed) | Requires ANTHROPIC_API_KEY |
| Capabilities | Full terminal access, can run commands | Text-only, no command execution |
| Installation | npm install -g @anthropic-ai/claude-code |
npm install @anthropic-ai/sdk |
| Automation | Uses --dangerously-skip-permissions |
Direct API calls |
| Cost | Free (uses Claude CLI session) | Pay per token |
You can also use Z.ai's GLM models (compatible with Claude Code) instead of Anthropic's models.
- Setup: Run
npm run setupand choose "GLM Model". - Manual Configuration:
- Get API key from Z.ai Model API.
- Set
ANTHROPIC_AUTH_TOKEN(your Z.ai key) andANTHROPIC_BASE_URL=https://api.z.ai/api/anthropicin your environment or.claude/settings.json.
@pr-automation/
├── src/
│ ├── index.js # Express server and webhook handler
│ ├── claude.js # Claude CLI integration (review + release note)
│ ├── git.js # Git operations (clone, update, validate)
│ ├── branch-matcher.js # Branch regex rules (prReview / releaseNote)
│ ├── metrics.js # Prometheus metrics collection
│ ├── logger.js # Logging configuration
│ ├── template-manager.js # Template management for PR reviews
│ └── config/
│ └── config.json # Templates + branch rules (prReview, releaseNote)
├── tests/ # Unit tests directory
│ ├── claude.test.js # Tests for Claude.js functionality
│ ├── git.test.js # Tests for Git operations
│ └── metrics.test.js # Tests for metrics collection
├── projects/ # Cloned repositories (volume mounted)
├── Dockerfile # Docker image with Claude CLI installed
├── docker-compose.yml # Docker Compose setup
├── jest.config.json # Jest testing configuration
├── package.json # Node.js dependencies and scripts
├── .env.example # Environment variables template
└── README.md # This file
GET /health
Returns the service status.
Response:
{
"status": "ok",
"message": "PR Automation service is running"
}POST /webhook/bitbucket/pr
Receives Bitbucket pull request webhooks.
Expected Headers:
x-event-key:pullrequest:created,pullrequest:updated, orpullrequest:comment_created
Response:
{
"message": "Webhook received successfully",
"prTitle": "Add new feature",
"enqueued": ["review", "create-release-note"],
"queuePosition": 2
}enqueued lists job types added to the queue (based on branch rules in config.json). One PR can enqueue both a review and a release-note job.
When manualTrigger.enabled is true, users can request an on-demand review by posting a PR comment. The trigger fires if either condition matches:
- Prefix command: Comment starts with
/review(optionally followed by text). - Mention + keyword: Comment mentions a configured bot (by ID or legacy name) and includes the keyword
review.
Examples:
/review
/review please check this PR
@review-bot review
@{12345:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee} review this
Bitbucket stores mentions in raw form as @{workspace:uuid}. Configure manualTrigger.botIds with these IDs (e.g. 12345:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee) for reliable mention matching. The legacy botNames (e.g. @review-bot) still works when BITBUCKET_USER or manualTrigger.botNames is set.
Manual comment triggers enqueue a review job directly (they bypass eventFilter.processOnlyCreated and branch pattern filters for prReview).
The system supports modular templates for customizing review behavior without code changes.
1. Create a custom template:
touch src/templates/custom/my-review.md2. Write your template with variables:
**Role:** You are a security-focused code reviewer.
**Goal:** Review {{repository}} for vulnerabilities.
**PR:** `{{prUrl}}`
## Security Checklist
- Check for SQL injection
- Verify input validation
- Review authentication logic
## Final Step: Output Metrics
```json
{"isLgtm": true/false, "issueCount": 0}3. Map repository to template:
Edit src/config/config.json (templates and branch rules):
{
"defaultTemplate": "default",
"repositories": {
"payment-api": "my-review"
},
"prReview": { "enabled": true, "targetBranchPatterns": [], "sourceBranchPatterns": [] },
"releaseNote": { "enabled": false, "targetBranchPatterns": ["^release-"], "sourceBranchPatterns": [] }
}Empty prReview.targetBranchPatterns = run review for all PRs. Set releaseNote.enabled and patterns to auto-generate release notes (e.g. when target branch matches ^release-). See TEMPLATE_GUIDE.md for branch rules.
4. Restart service:
docker-compose restart pr-automationUse these in your templates: {{prUrl}}, {{title}}, {{author}}, {{repository}}, {{sourceBranch}}, {{destinationBranch}}, {{description}}
security-focused- Security vulnerability analysisperformance-review- Performance bottleneck detectionquick-review- Fast review for small changes
📖 See TEMPLATE_GUIDE.md.
This project includes comprehensive unit tests to ensure code quality and reliability.
# Install dependencies
npm install
# Run all tests
npm test
# Run tests in watch mode (auto-reruns on file changes)
npm run test:watch
# Run tests with coverage report
npm run test:coverage# Install Claude CLI globally
npm install -g @anthropic-ai/claude-code
# Install dependencies
npm install
# Run tests to verify setup
npm test
# Create projects directory
mkdir projects
# Start in development mode with auto-reload
npm run devThe docker-compose.yml includes volume mounts for hot-reloading:
docker-compose upThe system executes Claude CLI like this:
claude --dangerously-skip-permissions \
-p "$(cat prompt.txt)" \
--model "sonnet" \
--output-format text--dangerously-skip-permissions: Skip interactive approval prompts (required for automation)-p: Provide prompt from file--model: Choose model (haiku, sonnet, opus)--output-format text: Get plain text output
The system automatically handles git operations:
- Clone: If repository doesn't exist, clones from Bitbucket
- Update: If repository exists, pulls latest changes
- Authentication: Uses token and username from environment variables
App Password (Token + User):
BITBUCKET_USER=your-username
BITBUCKET_TOKEN=your-token-hereNon-secret app settings live in src/config/config.json. Environment variables override config.json (for Docker or per-environment overrides). Secrets are never stored in config and must be set via environment.
| Variable | Required | Description |
|---|---|---|
BITBUCKET_TOKEN |
Yes | Bitbucket App Password or Token |
BITBUCKET_USER |
Yes | Bitbucket username |
BITBUCKET_WEBHOOK_SECRET |
Recommended | Webhook signature validation secret |
Also: SHELL and NODE_ENV are runtime/env-only.
Defaults are in src/config/config.json. You can override any of these via environment:
| config.json path | Env override | Default | Description |
|---|---|---|---|
server.port |
PORT |
3000 |
Server port |
claude.model |
CLAUDE_MODEL |
sonnet |
Claude model (e.g. haiku, sonnet, opus, glm-4.6) |
claude.timeoutMinutes |
CLAUDE_TIMEOUT_CONFIG |
10 |
Claude analysis timeout (minutes) |
claude.maxDiffSizeKb |
MAX_DIFF_SIZE_KB |
200 |
Max diff size in KB to include in prompt |
bitbucket.allowedWorkspace |
ALLOWED_WORKSPACE |
yourworkspace |
Bitbucket workspace to accept webhooks from |
bitbucket.nonAllowedUsers |
NON_ALLOWED_USERS |
- | Comma-separated display names to skip |
eventFilter.processOnlyCreated |
PROCESS_ONLY_CREATED |
false |
Only process PR creation events |
manualTrigger.enabled |
- | true |
Enable comment-based manual review trigger |
manualTrigger.prefixCommand |
- | "/review" |
Prefix command to trigger review (e.g. /review anything) |
manualTrigger.keywords |
- | ["review"] |
Keywords required for the mention-based trigger |
manualTrigger.botIds |
- | [] |
Bitbucket account IDs for mentions (e.g. 12345:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee) |
manualTrigger.botNames |
- | [] (auto-seeded from BITBUCKET_USER when empty) |
Legacy: display names for @mention matching |
metrics.persistence.* |
METRICS_PERSISTENCE_* |
- | Metrics persistence (enabled, type, path, saveIntervalMs) |
logging.* |
LOG_* |
- | Log level, file retention, console/file toggles |
circuitBreaker.* |
CB_* |
- | Circuit breaker threshold and reset timeout |
promptLogs.enabled / .path |
PROMPT_LOGS_* |
false, /app/prompt-logs |
Persist prompt logs to path |
Templates and branch rules: defaultTemplate, repositories, prReview, releaseNote are also in config.json (no env overrides by default).
curl http://localhost:3000/healthdocker-compose logs -f pr-automationdocker-compose exec pr-automation sh
claude --helpdocker-compose exec pr-automation ls -la /app/projectsdocker-compose exec pr-automation sh
cd /app/projects
git clone https://x-token-auth:YOUR_TOKEN@bitbucket.org/your-workspace/your-repo.gitdocker-compose restartdocker-compose down
docker-compose build --no-cache
docker-compose up -ddocker-compose downrm -rf projects/*
docker-compose restartThe webhook endpoint is secured with two layers of protection:
All webhook requests must include a valid HMAC-SHA256 signature in the X-Hub-Signature header. This ensures requests actually come from Bitbucket.
Only webhooks from the yourworkspace Bitbucket workspace are accepted. This prevents unauthorized access from other organizations.
-
Generate a webhook secret:
openssl rand -hex 32
-
Add to
.envfile:BITBUCKET_WEBHOOK_SECRET=your-generated-secret ALLOWED_WORKSPACE=yourworkspace
-
Configure in Bitbucket:
- Go to Repository Settings → Webhooks
- Add webhook URL:
https://bitbucket.tintinwinata.online/webhook/bitbucket/pr - Add the same secret in the "Secret" field
- Select triggers: PR Created, PR Updated
-
Restart service:
docker compose restart pr-automation
📖 See WEBHOOK_SECURITY.md for detailed configuration and troubleshooting.
The application exposes Prometheus metrics at /metrics endpoint for monitoring PR automation activities and Claude review performance.
- PR Created:
pr_created_total- Number of PRs created - PR Updated:
pr_updated_total- Number of PRs updated - LGTM Count:
claude_lgtm_total- Number of approvals from Claude - Issues Found:
claude_issues_found_total- Total count of all issues found (e.g., if 1 PR has 3 issues, adds 3 to counter) - Successful Reviews:
claude_review_success_total- PRs successfully reviewed - Failed Reviews:
claude_review_failure_total- Failed reviews (with error types) - Review Duration:
claude_review_duration_seconds- Histogram of review durations
curl http://localhost:3000/metricsSee PROMETHEUS.md for:
- Detailed metric descriptions
- Grafana dashboard examples
- Sample PromQL queries
Note: Prometheus is already configured in /workspace/monitoring/prometheus.yml to scrape metrics from pr-automation:3000.
By default, metrics are stored in memory and reset when the application restarts. You can enable metrics persistence to preserve metrics across restarts and container rebuilds.
Add these environment variables to your .env file:
METRICS_PERSISTENCE_ENABLED=true
METRICS_PERSISTENCE_TYPE=filesystem
METRICS_PERSISTENCE_PATH=./metrics-storage
METRICS_PERSISTENCE_SAVE_INTERVAL_MS=30000Filesystem (Recommended for most use cases)
- Stores metrics in a JSON file
- Simple and easy to inspect
- Works well for small to medium deployments
- Default storage type
SQLite (Recommended for larger deployments)
- Stores metrics in a SQLite database
- Better performance for high-volume metrics
- Requires
better-sqlite3package (automatically installed) - Falls back to filesystem if SQLite is unavailable
| Option | Description | Default |
|---|---|---|
METRICS_PERSISTENCE_ENABLED |
Enable/disable persistence | false |
METRICS_PERSISTENCE_TYPE |
Storage type: filesystem or sqlite |
filesystem |
METRICS_PERSISTENCE_PATH |
Path to store metrics (relative or absolute) | ./metrics-storage |
METRICS_PERSISTENCE_SAVE_INTERVAL_MS |
How often to save metrics (milliseconds) | 30000 (30 seconds) |
When using Docker, make sure to mount the metrics storage directory as a volume:
volumes:
- ./metrics-storage:/app/metrics-storageThis ensures metrics persist even when the container is rebuilt.
- On Startup: The application loads persisted metrics from storage and restores them to the Prometheus registry
- During Runtime: Metrics are automatically saved every 30 seconds (configurable via
METRICS_PERSISTENCE_SAVE_INTERVAL_MS) - On Shutdown: Metrics are saved one final time before the process exits
- Metrics persistence is opt-in - disabled by default
- If persistence fails to initialize, the application continues without persistence (logs a warning)
- Existing deployments without persistence continue to work as before
Metrics not persisting:
- Check that
METRICS_PERSISTENCE_ENABLED=trueis set - Verify the storage path is writable
- Check application logs for persistence-related errors
Permission errors:
- Ensure the storage directory exists and is writable
- In Docker, verify volume mounts are configured correctly
Feel free to contribute to this project! Whether you want to:
- 🐛 Report bugs or issues
- 💡 Suggest new features or improvements
- 🔧 Submit pull requests with fixes or enhancements
- 📖 Improve documentation or examples
- 🧪 Add tests or improve existing ones
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature-name - Make your changes and test them thoroughly
- Commit your changes:
git commit -m "Add your feature" - Push to your fork:
git push origin feature/your-feature-name - Open a Pull Request
I'm always open to discussing issues, reviewing PRs, or just chatting about the project!
Feel free to DM me on LinkedIn - I'd love to hear from you and help with any questions you might have.
Happy coding! 🚀