https://github.com/bborbe/github-pr-watcher
GitHub PR watcher — extracted from bborbe/maintainer. Watches PR events via the agent framework; image docker.io/bborbe/github-pr-watcher.
https://github.com/bborbe/github-pr-watcher
Last synced: 1 day ago
JSON representation
GitHub PR watcher — extracted from bborbe/maintainer. Watches PR events via the agent framework; image docker.io/bborbe/github-pr-watcher.
- Host: GitHub
- URL: https://github.com/bborbe/github-pr-watcher
- Owner: bborbe
- License: bsd-2-clause
- Created: 2026-07-04T21:45:02.000Z (17 days ago)
- Default Branch: master
- Last Pushed: 2026-07-10T19:27:58.000Z (11 days ago)
- Last Synced: 2026-07-20T08:32:15.386Z (1 day ago)
- Language: Go
- Size: 171 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# github-pr-watcher
Polls the GitHub Search API for open pull requests and publishes a `CreateTaskCommand` to Kafka
for each new or force-pushed PR so the [`github-pr-review-agent`](https://github.com/bborbe/github-pr-review-agent) picks it up automatically.
## Links
Dev:
https://dev.quant.benjamin-borbe.de/admin/maintainer-watcher-github-pr/setloglevel/3
https://dev.quant.benjamin-borbe.de/admin/maintainer-watcher-github-pr/check
https://dev.quant.benjamin-borbe.de/admin/maintainer-watcher-github-pr/trigger?url=https://github.com/owner/repo/pull/123
Prod:
https://prod.quant.benjamin-borbe.de/admin/maintainer-watcher-github-pr/setloglevel/3
https://prod.quant.benjamin-borbe.de/admin/maintainer-watcher-github-pr/check
https://prod.quant.benjamin-borbe.de/admin/maintainer-watcher-github-pr/trigger?url=https://github.com/owner/repo/pull/123
## How It Works
The watcher runs a `user:` GitHub Search query on a configurable interval. On each poll
it compares the PR's current head SHA against the value stored in the cursor; if the SHA has
changed (force-push) the PR is re-submitted as a new task. The cursor is persisted to
`/data/cursor.json` between polls so a restart does not re-trigger every known PR.
Two independent decision chains run per PR — see [`docs/watcher-decision-chains.md`](https://github.com/bborbe/maintainer/blob/master/docs/watcher-decision-chains.md):
- **`TaskCreationFilter`** — should we create a task at all? (drafts, bots, WIP titles, age, allowlist)
- **`TrustGate`** — given a task is created, auto-process or route to `human_review`? (trusted authors)
## Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
| `GH_TOKEN` | yes | — | GitHub personal access token (read scope sufficient) |
| `KAFKA_BROKERS` | yes | — | Comma-separated Kafka broker list |
| `STAGE` | yes | — | Deployment stage (`dev` or `prod`) |
| `TRUSTED_AUTHORS` | yes | — | Comma-separated trusted GitHub logins; empty list refuses startup |
| `LISTEN` | no | `:9090` | HTTP listen address (`/healthz`, `/readiness`, `/metrics`, `/check`, `/trigger`) |
| `POLL_INTERVAL` | no | `5m` | Poll interval (Go duration string) |
| `REPO_SCOPE` | no | `bborbe` | GitHub user or org to search for PRs |
| `REPO_ALLOWLIST` | no | — | Comma-separated host-qualified repo allowlist (`host/owner/repo`); empty means allow-all |
| `BOT_ALLOWLIST` | no | `dependabot[bot],renovate[bot]` | Comma-separated bot author logins to skip |
| `MAX_PR_AGE` | no | `2160h` (90d) | Skip PRs older than this; empty disables |
| `BACKFILL_DURATION` | no | `720h` (30d) | On cold start, backdate the initial cursor by this; empty disables |
| `SENTRY_DSN` | no | — | Sentry DSN for error tracking |
| `SENTRY_PROXY` | no | — | HTTP proxy URL for Sentry transport |
### `REPO_ALLOWLIST` syntax
Entries are comma-separated. A leading `!` marks an exclusion. A target is allowed iff `(includes is empty OR any include matches) AND (no exclude matches)`; excludes always override includes.
| Entry shape | Example | Meaning |
|---|---|---|
| Literal include | `github.com/bborbe/maintainer` | Allow exactly this repo |
| Wildcard include | `github.com/bborbe/*` | Allow every repo under this owner |
| Literal exclude | `!github.com/bborbe/go-skeleton` | Reject exactly this repo (overrides any matching include) |
| Wildcard exclude | `!github.com/bborbe/*` | Reject every repo under this owner |
An allowlist consisting of only exclude entries is treated as allow-all-except: every target passes the include gate, and only the exclude gate filters. Example: `REPO_ALLOWLIST=!github.com/bborbe/go-skeleton` rejects go-skeleton and allows every other repo (including all other bborbe repos). To allow every bborbe repo except go-skeleton, write `github.com/bborbe/*,!github.com/bborbe/go-skeleton`.
## HTTP Endpoints
| Path | Method | Purpose |
|---|---|---|
| `/healthz` | GET | Liveness probe (always returns 200 OK) |
| `/readiness` | GET | Readiness probe (always returns 200 OK) |
| `/metrics` | GET | Prometheus metrics |
| `/check` | POST | Run a poll cycle in the background; returns 200 immediately |
| `/trigger` | POST | Fire a single-PR review by URL (`?url=`); reuses the filter chain and trust evaluation |
### Single-PR Trigger Known Limit
If a vault task already exists for the same `(PR, SHA)`, the controller's `create-if-not-exists` is idempotent and no fresh agent Job spawns. To force a re-run in that case, reset the vault task's frontmatter (`phase`, `status`, `trigger_count`) manually OR push a new commit so the SHA changes.
## Development
```bash
make test # run unit tests
make generate # regenerate counterfeiter mocks
make precommit # format + lint + test + security checks
```
## Cursor Mechanism
The cursor at `/data/cursor.json` records the timestamp of the most-recently-seen PR update plus
a map of `task_id → head_sha`. On cold start (missing file) the cursor is initialised to the
process start time minus `BACKFILL_DURATION`, so only PRs updated within that window are
submitted. Force-push detection compares the stored head SHA for a known PR against the value
returned by the current poll; a mismatch publishes a new `CreateTaskCommand` with the new SHA.
A corrupt cursor refuses startup — see `pkg/cursor.go`.
## Relationship to pr-reviewer
This service feeds tasks into the [`github-pr-review-agent`](https://github.com/bborbe/github-pr-review-agent) Pattern B Job via Kafka: for every new or
force-pushed PR it publishes a `CreateTaskCommand` that the agent task controller picks up and
spawns into a per-task K8s Job. The agent runs `/coding:pr-review` and posts the verdict back to
the PR.
See [`docs/architecture.md`](https://github.com/bborbe/maintainer/blob/master/docs/architecture.md) for the full pipeline.
## License
BSD 2-Clause License. See [LICENSE](LICENSE).