diffmate

A Chrome extension that puts one line on every file of a GitHub pull request, and answers the notes you leave on a line through Claude Code.

It drives the Claude Code you already run. Everything stays on your machine, and nothing is posted to GitHub.

github.com/chaeeun037/diffmate/pull/12/changes
4 changed files
.github/workflows/
src/
constants/
hooks/
lib/
▾ src/lib/retryFetch.ts
● high Added an attempt limit to the retry wrapper. Get the stop condition wrong and a failed request repeats until the tab closes.
…@@ -12,6 +12,14 @@
13 let attempt = 0;
14 while (attempt < MAX_ATTEMPTS) {
questionansweredL14
Why a loop instead of recursion? Doesn't this block the caller?
It does not. Every attempt awaits, so other work runs in between. Recursion adds a stack frame per retry, and the original error ends up buried under them.
15 const res = await fetch(url, init);
▸ src/hooks/useSession.ts
◐ medium Reworked the hook that reads the session. The value is undefined on first render, so branching on it flashes the logged-out screen.
✓ questionfile
Why read the session in a hook instead of passing it down as a prop?
Five screens read it. Passing it down would thread it through every layout in between.
▸ src/constants/LogType.ts
○ skim Registers three log names.
notes 2 · unanswered 0 · summaries 4

Features

File summaries — one line per changed file. It says what you changed and what breaks if it is wrong. The marker beside it is how far the damage spreads (high, medium, skim). GitHub lists files by path, so the risky one often sits at the very bottom; skimming the list is enough to decide where to start.

▸ src/api/client.ts
● high Moved token refresh into the interceptor. If refresh fails and the original request is sent again, it retries forever.

Notes — hover a line and a button appears. Click it, type, and a card attaches to that line; answers and follow-ups stack on the same card. Anything other people should see belongs in a GitHub comment — this one is between you and Claude, and nobody else sees it.

▾ src/api/client.ts
42 if (!refreshed) return Promise.reject(error);
questionansweredL42
So it just bails when refresh fails? What happens to whatever I was typing?
It is gone. A failed refresh is treated as a logout and sends you to the login screen. Keeping the input would need a draft store, which is out of scope here.
⌘Enter to save

Changed-files tree — every changed file shows up as a tree above the file list. Each file carries its risk marker (● high · ◐ medium · ○ skim) and its note count, and a folder with a single subfolder folds into one row, the way GitHub does it. Click a file to jump to it. Fold the panel away and it stays folded next time.

3 changed files
src/api/
tests/api/

Kinds and answers at a glance — every card says whether it is a question, a request, or a memo, and questions and requests also say whether Claude has answered. Memos are just for you, so they get no answer badge.

questionunansweredL42
Does this still catch a 401 from the refresh call itself?
requestansweredL18–24
Pull the retry delay out into a constant.
memofile
Compare with how the mobile app handles this later.

Jump between notes — ⌥J goes to the next note and ⌥K to the previous one. The card you land on flashes for a moment. While you are typing a note, the keys are left alone.

How it works

The extension never talks to Claude directly. A local file and your terminal sit in between.

Browser (extension)  ──writes──▶  ~/.diffmate/<repo>/<pr>.json
                                        │  ▲
                                        ▼  │  read and written through the CLI
                              Claude Code in your terminal
                              "fill in the file summaries" / "answer my notes"
  1. Fill the summaries — right after opening the pull request, ask Claude Code in your terminal. One line per file goes into the json, and it is already there when you open the browser.
  2. Leave notes — written in the browser, saved to the same json.
  3. Get answers — go back to the terminal and say "answer my notes". The answers go into the json and show up on the cards. No refresh needed — it re-reads every three seconds.