{"id":51270367,"url":"https://github.com/piekstra/cr-daemon","last_synced_at":"2026-06-29T17:36:12.929Z","repository":{"id":365247390,"uuid":"1271225867","full_name":"piekstra/cr-daemon","owner":"piekstra","description":"macOS menu-bar app that auto-reviews assigned GitHub PRs via the cr CLI — careful on the GitHub API, robust across sleep/wake.","archived":false,"fork":false,"pushed_at":"2026-06-16T14:14:00.000Z","size":64,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T15:12:25.546Z","etag":null,"topics":["code-review","github","launchd","macos","menu-bar","pull-requests","swift"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/piekstra.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-16T13:04:38.000Z","updated_at":"2026-06-16T14:18:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/piekstra/cr-daemon","commit_stats":null,"previous_names":["piekstra/cr-daemon"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/piekstra/cr-daemon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piekstra%2Fcr-daemon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piekstra%2Fcr-daemon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piekstra%2Fcr-daemon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piekstra%2Fcr-daemon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piekstra","download_url":"https://codeload.github.com/piekstra/cr-daemon/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piekstra%2Fcr-daemon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34937370,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-review","github","launchd","macos","menu-bar","pull-requests","swift"],"created_at":"2026-06-29T17:36:08.398Z","updated_at":"2026-06-29T17:36:12.832Z","avatar_url":"https://github.com/piekstra.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cr-daemon\n\nA native macOS **menu-bar app** that watches GitHub for pull requests assigned to a\ndedicated reviewer account and reviews them automatically with the\n[`cr`](https://github.com/open-cli-collective/codereview-cli) CLI — so a separate identity can\napprove your PRs and satisfy a branch-protection \"require 1 approval\" rule without you approving\nyour own work.\n\nIt is built to be **calm on the GitHub API** (you should never see a request flood) and\n**robust on a laptop** (survives sleep, lid-close, power changes, and network drops with no\nduplicate process and no lost work).\n\n\u003e cr-daemon is a thin companion around the public `cr` CLI. It does not bundle or depend on any\n\u003e private tooling — it shells out to `cr review` and talks to the GitHub REST API.\n\n---\n\n## How it works\n\n```\n   ┌─────────────┐   review-requested:\u003creviewer\u003e    ┌──────────────────┐\n   │ GitHub      │ ───────────────────────────────▶ │ Search poller    │  (source of truth)\n   │ Search API  │   (polled, conditional, jittered) │ in cr-daemon     │\n   └─────────────┘                                   └────────┬─────────┘\n                                                              │ new assignment\n                                                              ▼\n                                              ┌───────────────────────────────┐\n                                              │ Queue (atomic JSON, crash-safe)│\n                                              └───────────────┬───────────────┘\n                                                              │ one at a time\n                                                              ▼\n                                   cr review \u003curl\u003e --profile reviewer --json\n                                              │\n                                              ▼  posts findings / approval AS the reviewer account\n                                     ✅ approval satisfies branch protection\n```\n\n- **Discovery is the GitHub Search API** (`is:open is:pr review-requested:\u003creviewer\u003e`), which\n  reflects *current* assignment state and self-heals if anything is missed. Polled on a jittered\n  interval with conditional requests.\n- **Action is `cr review`**, run under a dedicated `cr` profile whose token belongs to the reviewer\n  account. `cr` decides approve vs. comment vs. request-changes — cr-daemon never blanket-approves.\n- The reviewer account is a **separate GitHub identity** from the PR author, so its approval counts\n  toward branch protection.\n\n## Requirements\n\n- macOS 13 (Ventura) or later.\n- The [`cr` CLI](https://github.com/open-cli-collective/codereview-cli)\n  (`brew install open-cli-collective/tap/codereview-cli`), configured with a working LLM adapter.\n- A **dedicated GitHub account** to review/approve as (a machine account is permitted by GitHub's\n  Terms alongside your personal account).\n- Xcode Command Line Tools (`xcode-select --install`) to build — full Xcode is **not** required.\n\n## Install\n\n```bash\ngit clone https://github.com/piekstra/cr-daemon.git\ncd cr-daemon\n./Scripts/setup-reviewer.sh    # one-time: stage the reviewer token (see below)\n./Scripts/install.sh           # build, install to ~/Applications, load the LaunchAgent\n```\n\nThe icon appears in your menu bar. It's also launchable from Spotlight (⌘-Space → \"cr-daemon\").\n\n## Reviewer setup\n\ncr-daemon reviews as a **separate account** (referred to here as `\u003creviewer\u003e`). One-time setup:\n\n1. **Create the account** and a **classic Personal Access Token** on it with scopes\n   `repo` and `read:org`. (A classic token is used so the same setup works if you later enable\n   notification-based triggers; `repo` is what lets it read PRs and submit approvals.)\n2. **Give it _write_ (push) access** to the repos you want it to review: add `\u003creviewer\u003e` as a\n   collaborator (or org member) with **push** — read/triage is *not* enough. GitHub silently\n   ignores an approving review from a user without write access, so the PR stays `REVIEW_REQUIRED`\n   and branch protection is never satisfied even though the review shows as approved. It only needs\n   access where you actually request its review — grant incrementally. See\n   [docs/setup.md](docs/setup.md) for the full rationale.\n3. **Stage the token**: `./Scripts/setup-reviewer.sh` creates a dedicated `cr` profile and a\n   Keychain item from the token (read via stdin — never on the command line). It verifies the\n   profile resolves to `\u003creviewer\u003e`.\n4. **Request its review** on a PR. Within a poll interval cr-daemon picks it up and runs `cr`.\n\ncr-daemon **refuses to run reviews** if the configured `cr` profile resolves to anyone other than\nthe configured reviewer login — a guard against accidentally reviewing as yourself.\n\n## Review agents (recommended)\n\nReview *quality* depends on cr's **trusted review agents** — specialized reviewer personas cr\nselects per PR. With none configured, cr does a single generic pass and reports\n`Reviewers: unavailable`. Point cr at an agent source; the\n[Open CLI Collective set](https://github.com/open-cli-collective/codereview-cli) (shipped in that\nrepo's `.codereview/agents/`) is a good default:\n\n```bash\n# copy a trusted snapshot OUT of the git worktree, then register it\ncp -R ~/Dev/codereview-cli/.codereview/agents \"$HOME/Library/Application Support/codereview/\"\ncr config agent-source add \"$HOME/Library/Application Support/codereview/agents\" --profile reviewer\ncr agents list --profile reviewer   # verify (no worktree warning)\n```\n\nSee **[docs/agents.md](docs/agents.md)** for the trust caveat (use a stable, non-PR-mutable path),\nverification, and notes on writing your own agents.\n\n## Configuration\n\n`~/Library/Application Support/cr-daemon/config.json` (hot-reloadable via the menu). Defaults are\nconservative.\n\n| Key | Default | Meaning |\n|---|---|---|\n| `reviewer_login` | `piekstra-dev` | GitHub login the daemon reviews as |\n| `reviewer_keychain_account` | `piekstra-dev` | Keychain account holding the watcher token (service `cr-daemon`) |\n| `cr_profile` | `reviewer` | cr profile invoked (`cr review --profile …`) |\n| `orgs` | `[piekstra, strikeforcezero, open-cli-collective]` | Owner/org allowlist (case-insensitive) |\n| `autonomy` | `auto` | `auto` = live review on assignment; `confirm` = dry-run then approve from the menu |\n| `search_poll_interval_seconds` | `90` | Base poll interval (jittered) |\n| `core_rate_floor` / `search_rate_floor` | `500` / `5` | Stop spending a bucket below this many remaining |\n| `review_timeout_seconds` | `1200` | Wall-clock kill for a single `cr` run |\n| `per_pr_attempt_cap` | `3` | Attempts before a PR is marked failed — failures are auto-retried (after ~1h, and whenever `cr` upgrades), not permanently quarantined |\n| `daily_review_cap` | `50` | Global runaway guard |\n| `author_allowlist` | `null` | If set, only act on PRs by these authors |\n| `tier_label_profiles` | `{cr:large→reviewer-large}` | PR label → cr profile, to route a tagged PR to a deeper model tier ([docs/agents.md](docs/agents.md#deeper-reviews-on-demand-the-crlarge-label)) |\n| `notify_on` | all `true` | macOS notifications for approvals / findings / errors |\n| `paused` | `false` | Master pause |\n\n## Being gentle on the GitHub API\n\nThis was a first-class design goal. cr-daemon:\n\n- Treats the **Search API as the source of truth** and polls it on a **jittered interval** (never a\n  tight loop), with **conditional requests** so unchanged responses are free `304`s.\n- Tracks the **core (5000/hr) and search (30/min) buckets independently** and **stops spending**\n  either below a configurable floor.\n- Honors `Retry-After` on secondary-limit `403`/`429` responses and otherwise backs off with\n  **full-jitter exponential backoff**, behind a **circuit breaker**.\n- Computes reset waits from the **absolute `X-RateLimit-Reset`** epoch, so a sleep across a reset\n  boundary never leaves it stuck throttled.\n- **Serializes reviews** (one `cr` at a time) and passes `--max-concurrency 1`, so the watcher and\n  `cr` never compound pressure on the shared token.\n\nSee [docs/rate-limiting.md](docs/rate-limiting.md).\n\n## Laptop robustness\n\n- A **launchd LaunchAgent** supervises the process: it relaunches only on a crash\n  (`KeepAlive = { SuccessfulExit = false, Crashed = true }`), throttles relaunches, and starts at\n  login. A clean **Quit** stays quit.\n- A **flock single-instance lock** means launching a second copy (e.g. from Spotlight) just re-opens\n  the menu — never a duplicate process.\n- A **crash-loop guard** drops into a visible \"safe mode\" instead of thrashing.\n- **Sleep/wake + network** are an explicit state machine: on sleep an in-flight review is interrupted\n  and re-queued; on wake it waits for the network before polling and recovers any interrupted review\n  with `cr review --retry-posts`.\n- The queue is an **atomically-rewritten JSON file** plus an append-only event log, so assignments\n  and in-flight state survive crashes and restarts.\n\nSee [docs/architecture.md](docs/architecture.md).\n\n## Menu\n\nIdentity + status, both rate buckets, the live queue (Open / Review now / Skip, or Approve \u0026 post in\nconfirm mode), recent results, Pause/Resume, Poll now, Edit/Reload config, Open logs, Open data\nfolder, Quit.\n\n## Signing \u0026 Gatekeeper\n\nThe build is **ad-hoc signed**, not notarized — it's a local, single-user tool. macOS may ask you to\nallow it on first launch. Because an ad-hoc signature changes on every rebuild, macOS may re-prompt\nfor Keychain access after an upgrade; cr-daemon avoids this for its token by reading through\n`/usr/bin/security` (a stable, Apple-signed tool) rather than the Keychain APIs directly.\n\n## Uninstall\n\n```bash\n./Scripts/uninstall.sh   # removes the LaunchAgent + app; keeps your config/state/logs\n```\n\n## FAQ\n\n**Will it approve everything?** No. cr-daemon only *triggers* `cr` on assignment; `cr`'s own review\ndecides approve / comment / request-changes.\n\n**Does it need to run all the time?** It only acts while running. launchd keeps it alive and restarts\nit at login; closing the lid or sleeping is fine.\n\n**Can I see what it did?** Yes — the menu shows recent outcomes, and\n`~/Library/Logs/cr-daemon/cr-daemon.log` is a structured (token-redacted) JSONL log.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) and [AGENTS.md](AGENTS.md). Licensed under\n[MIT](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiekstra%2Fcr-daemon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiekstra%2Fcr-daemon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiekstra%2Fcr-daemon/lists"}