# gh-monitor — Design Doc **Status:** Pending Andrew's review **Repo:** coding-with-hans-heinemann/hans-tools **Author:** Hans Heinemann --- ## What It Does Polls the GitHub API for activity on watched repositories and fires OpenClaw system events to wake Hans when action is needed. Hans can then read the PR, respond to comments, push fixes, or request changes — all without a public webhook endpoint. --- ## Scope Initial scope: PRs only. Issues, CI, and deployments out of scope for v1. Events monitored: - New review submitted (approved, changes requested, commented) - New PR review comment posted - New PR issue comment posted - PR merged or closed Events NOT monitored in v1: - CI/check status - Issue activity - Dependabot alerts --- ## Architecture ``` cron (every 5 min) └── gh-monitor/poll.py ├── reads config/watched.yaml (repos + filter rules) ├── reads state/last_seen.json (per-repo event cursor) ├── calls GitHub API via gh CLI (no extra credentials) ├── diffs against last_seen ├── for each new event: │ └── fires openclaw system event (text summary) └── writes updated last_seen.json ``` Hans receives OpenClaw system event → session wakes → Hans reads + acts. --- ## Config — watched.yaml ```yaml repos: - owner: coding-with-hans-heinemann repo: the-agency notify_on: - review_submitted - review_comment - issue_comment - pr_closed ``` Multiple repos supported. Per-repo filter rules. --- ## State — last_seen.json Tracks the timestamp of the last processed event per repo. On each poll, only events newer than this cursor are processed. Prevents duplicate alerts. ```json { "coding-with-hans-heinemann/the-agency": { "last_event_at": "2026-03-15T17:00:00Z" } } ``` On first run (no state file), cursor is set to now — no backfill of old events. --- ## Notification Format OpenClaw system event text: ``` [gh-monitor] PR #1 "feat: Phase 2" — Andrew left a review comment: "The escalation retry logic looks good but can you add a test for the blocked case?" https://github.com/coding-with-hans-heinemann/the-agency/pull/1#discussion_r12345 ``` One event per notification. If multiple events arrive in one poll cycle, they fire as separate system events in sequence. --- ## GitHub API Access Uses `gh` CLI (already installed, already authenticated as hansheinemann). No new credentials needed. All API calls go through `gh api`. Endpoints used: - `GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews` - `GET /repos/{owner}/{repo}/pulls/{pull_number}/comments` - `GET /repos/{owner}/{repo}/issues/{pull_number}/comments` - `GET /repos/{owner}/{repo}/pulls` (list open PRs) Rate limit: 5,000 requests/hour for authenticated requests. At 5-min poll intervals across a handful of repos, this is nowhere near the limit. --- ## Schedule Every 5 minutes via macOS `launchd` (LaunchAgent plist). `poll.py` runs as a plain shell process — no agent session, no tokens consumed during polling. The main session is only woken when `poll.py` finds new activity and calls `openclaw system event`. On a quiet repo this costs zero tokens most of the day. The LaunchAgent can be unloaded/loaded via `launchctl` to pause/resume polling without touching the code. --- ## Error Handling - GitHub API errors: log to `state/errors.log`, skip that repo for this cycle - Malformed API response: log and skip - Missing state file: create fresh with cursor = now - `gh` CLI not found: exit with error message Errors do NOT fire system events (avoid alert fatigue from transient API blips). If errors persist for >3 consecutive cycles, fire one alert to Hans. --- ## Security - No webhook endpoint — nothing exposed to the internet - No secrets stored in the repo — `gh` CLI handles auth via its own keychain - State files excluded from git via .gitignore - Read-only GitHub API access needed (no write scopes required for polling) --- ## Out of Scope (v1) - Filtering by PR author - Filtering by comment author - Digest mode (batch multiple events into one notification) - Slack/email delivery (OpenClaw system event only) - CI/check status monitoring