Pullwatch keeps the pull requests you care about visible without you having to live on github.com. It uses the GitHub session your browser already has, so there are no personal access tokens, no OAuth app, and no sign-in flow to worry about. A Manifest V3 service worker quietly refreshes review requests, the PRs you authored, and your recently merged work in the background, and it backs off politely when GitHub asks it to.
Add to Chrome from the Chrome Web Store.
From source: see Development below, then load the unpacked dist/ folder in chrome://extensions.
| Session based access | Reads the GitHub HTML you can already see while signed in. No personal access tokens, no OAuth, nothing to authorize. |
| Three tab inbox | To review (pending vs already reviewed), Authored (sorted by review state: changes requested, approved, pending, draft), and Merged (recently shipped). |
| Notifications | Optional desktop alerts and sounds for assigned (to review) and merged PRs. The Authored tab is for visibility only (no toasts). Draft alerts for assigned PRs are off by default and can be turned on. |
| Themes | 35 built-in DaisyUI themes on Tailwind CSS 4. |
| Background sync | Default refresh cadence is 3 minutes (see FETCH_INTERVAL_MINUTES in extension/common/constants.ts). It pauses when you go offline. |
| Resilient parsing | A three stage parser handles both the new and legacy GitHub list pages. Regex updates can be shipped from a public config repo without releasing a new extension build. |
| Fast popup | The UI hydrates instantly from chrome.storage.local so the panel is filled before any network call. |
Pullwatch is built so that you do not have to take its word for it. The whole codebase is open and the rules below are easy to verify. The full policy is in PRIVACY.md and on the docs site.
- No tokens. No OAuth app. Pullwatch never asks for a token and never creates an OAuth integration on your account. It reads the same pages your browser would render if you typed
github.com/pullsin the address bar yourself. - Outbound network (four host origins, all declared in the manifest). Pullwatch does not run its own servers and does not use analytics or third-party SDKs. The only destinations it contacts are:
https://github.com/*: the background worker fetches your signed-in pulls list HTML; the popup may open PR links and load pages on this origin using your existing session.https://avatars.githubusercontent.com/*: avatar images shown next to PR rows in the popup.https://raw.githubusercontent.com/dragosdev-code/pr-live-config/*: a publicpatterns.jsonfile used to update parser regexes without a new extension release.https://www.githubstatus.com/*: GitHub’s public Statuspage API (summary.json) so outage banners can be corroborated against real Pull Requests incidents. No credentials are sent; responses are cached locally inchrome.storage.local.
- Your data stays on your machine. PR lists, route hints, rate limit state, and other operational data live in
chrome.storage.localon this device only. Your appearance and notification preferences live inchrome.storage.syncso Chrome can carry them across your own signed in Chrome instances if you have Chrome sync turned on. Nothing is uploaded anywhere by Pullwatch itself. - Non-goals. Pullwatch does not act on PRs for you, does not write anything back to GitHub, and does not sync your PR data across devices. It is read only by design.
Pullwatch has two halves: a background service worker that fetches and parses pages from GitHub, and a popup that renders the result.
The service worker wakes up on a Chrome alarm every 3 minutes, asks GitHub for your pulls list HTML using the cookie your browser already has, runs the HTML through a three stage parser (embedded JSON, then the new dashboard HTML, then the legacy HTML), and writes the result into chrome.storage.local. If GitHub returns a 429, an exponential backoff kicks in. The popup itself never calls GitHub. When you open it, it pulls the last persisted lists straight from chrome.storage.local before the first paint, then listens for storage updates while it stays open.
When GitHub returns a transport error or its Statuspage reports a Pull Requests incident, Pullwatch keeps the last-known list and shows an outage banner over the top of it; the banner links to githubstatus.com only when the cached snapshot corroborates a real incident. The full reason taxonomy and the integrity layer that catches "200 OK with an incomplete list" are documented in the GitHub Health and Outages docs cluster.
flowchart LR
subgraph ui [Popup]
React[React UI]
RQ[TanStack Query]
end
subgraph bg [Service worker]
Alarm[AlarmService]
Events[EventService]
GH[GitHubService]
Store[chrome.storage.local]
end
React --> RQ
RQ -->|runtime messages| Events
Alarm -->|onAlarm| Events
Events --> GH
GH -->|fetch GitHub HTML| GitHub[github.com]
Events --> Store
React --> Store
For the longer tour of the service worker lifecycle, the parser waterfall, the remote pattern config, and the popup hydration flow, see the Architecture Overview in the docs site (full docs).
GitHub changes its DOM from time to time, and a small change can break a regex that the parser relies on. To catch this before users do, Pullwatch ships a canary suite that runs every hour as a GitHub Action.
It has two tiers. Tier 1 is a public baseline that fetches a known repository's pulls page with no credentials and asserts that the parser still produces well formed PRs. Tier 2 uses Playwright to sign two real bot accounts into GitHub and exercise both the legacy and the new dashboard experiences end to end. If either tier fails, the workflow posts a Discord alert with a link to the failing run and an HTML snapshot for triage. The full runbook lives at canary/DOM_CHANGE_RUNBOOK.md.
Each entry below explains what the package actually does inside Pullwatch, not just that it is installed.
| Area | Package | What it does here |
|---|---|---|
| UI | React 19 | Renders the popup and settings. |
| Data | @tanstack/react-query | Holds PR lists in the popup; hydrated from chrome.storage.local before the first paint. |
| State | zustand | Small UI stores for global error, debug mode, and tab control. |
| Forms | react-hook-form | Powers the settings forms in the settings overlay. |
| Validation | valibot | Validates the remote patterns.json at runtime before any regex is compiled or stored. |
| Dates | date-fns | Renders relative timestamps on PR rows ("3h ago", etc.). |
| Icons | @heroicons/react | Iconography used across the popup. |
| Animation | @react-spring/web | List entrance animations and the theme picker ripple effect. |
| A11y | react-focus-lock | Traps focus inside the settings overlay so keyboard users do not escape it by accident. |
| Styling | tailwindcss 4 + daisyui 5 | Styling and the 35 ready made themes. |
| Build | vite 8 + typescript ~5.8 | Builds the popup, service worker, and offscreen document. Strict TypeScript across the repo. |
| Static assets | vite-plugin-static-copy | Copies the manifest, icons, and offscreen HTML into dist/. |
| Unit tests | vitest + @testing-library | Test runner and React testing helpers. |
| Browser tests | playwright | Drives the canary's Tier 2 logins and screenshot capture. |
| Lint | oxlint | Fast linter used across the repo. |
All permissions are declared in public/manifest.json. Each one exists for a single, narrow reason.
| Permission | Why it exists |
|---|---|
storage |
Saves your PR lists, settings, route hints, and rate limit state inside Chrome's own storage on this device. |
notifications |
Lets Pullwatch show optional desktop alerts when something on your inbox changes. You can turn this off per category. |
alarms |
Lets the background refresh run on a schedule (every 3 minutes by default) without keeping a tab open. |
offscreen |
Manifest V3 service workers cannot play audio. The offscreen document is used to play notification sounds and nothing else. |
https://github.com/* |
Reads the signed in pulls pages your browser would already render. This is where every PR row comes from. |
https://avatars.githubusercontent.com/* |
Loads avatar images shown next to each PR. |
https://raw.githubusercontent.com/dragosdev-code/pr-live-config/* |
Downloads the public regex config file used by the parser. See the pr-live-config repo. |
https://www.githubstatus.com/* |
Fetches GitHub’s public status API so outage banners match real Pull Requests incidents (see How it works above). No account data is sent. |
Use the same wording in the store’s permission and host-access fields so the listing matches this README.
storage: Saves PR lists, settings, route hints, and rate-limit state on this device only.notifications: Optional desktop alerts when your inbox changes (per-category; can be disabled).alarms: Background refresh on a schedule without keeping a GitHub tab open.offscreen: Plays notification sounds; MV3 service workers cannot useAudioContextdirectly.https://github.com/*: Reads signed-in pulls pages the user could already open in the browser; no tokens or OAuth.https://avatars.githubusercontent.com/*: PR author avatars in the popup UI.https://raw.githubusercontent.com/dragosdev-code/pr-live-config/*: Public parser regex updates (data only, validated before use).https://www.githubstatus.com/*: Public outage status for accurate “GitHub may be down” banners; no user credentials.
Single purpose: Read-only GitHub PR inbox in the toolbar: track reviews, authored PRs, and merges using the browser’s existing GitHub session.
Prerequisites: Node.js 18 or later, plus npm (or pnpm).
git clone https://github.com/dragosdev-code/pullwatch.git
cd pullwatch
npm installBuild the extension (output goes into dist/):
npm run buildThen in Chrome: open chrome://extensions, enable Developer mode, click Load unpacked, and choose the dist folder.
Other useful scripts
npm run devruns the Vite dev server. Useful for fast UI iteration in a browser tab; the service worker is not wired up here.npm testandnpm run test:runrun the unit tests with Vitest.npm run canary:testruns the canary suite locally.npm run test:remote-patternsvalidates the productionpatterns.jsonagainst the schema. There are also:staging,:production, and:production:parityvariants.npm run lintruns oxlint.npm run ziprunsnpm run build, then writespullwatch.zipat the repo root (productiondist/contents only; excludes marketingpullwatch-view.gif, analyzerstats.html, and*.map). Use this archive for Chrome Web Store uploads.npm run iconsregeneratespublic/logo-{16,32,48,128}.pngfrompublic/logo.png(viasharp). The icons are already committed, so a normal build does not need this: it is a one-time step run only when the logo changes.
Squash minigame architecture and simulation contracts: src/components/squash-minigame/docs/README.md.
Pullwatch is a personal project that I build and maintain on my own, so I am not accepting code contributions for the moment. That said, bug reports and feature ideas are very welcome and genuinely useful.
If something is off, please open an issue at github.com/dragosdev-code/pullwatch/issues. What helps most:
- The extension version (visible in
chrome://extensions). - The browser and OS you are on.
- A short description of what you expected and what you saw.
- A screenshot if it is a UI issue.
- The console logs. They are the single most useful thing you can attach: they are what let me reproduce and debug the problem. See Grabbing logs below for where to find them and what to paste.
Pullwatch logging is always on, including in production builds, so you do not need a special debug build to capture it. Every line is prefixed [DEBUG], [DEBUG WARN], or [DEBUG ERROR], so you can filter the console on [DEBUG and copy only Pullwatch's output.
- Background (service worker) logs cover the scheduled refresh, page parsing, rate-limit backoff, and notifications. Open
chrome://extensions, enable Developer mode, find the Pullwatch card, click the blue service worker link, then open the Console tab and filter on[DEBUG. - Popup logs cover what the UI does when you open it. Right-click the open popup, choose Inspect, then open the Console tab.
The most useful thing you can attach is the console output captured while reproducing the bug: open the relevant console first, trigger the problem, then paste the lines from around that moment (any [DEBUG ERROR] lines especially).
Thanks for taking the time. It really does help.
Pullwatch is a personal project I build in my spare time. If it saves you a tab or two on GitHub, a tip on Ko-fi helps keep it maintained. You can also use the Sponsor button on this repository (same link).
MIT.

