A free, open-source Claude Code hook that writes the why behind your agent's pull requests — the decisions, trade-offs, and assumptions — into the PR itself, the moment it's opened.
I keep approving agent PRs I never actually reasoned about
A few weeks ago I approved one of my own agent's pull requests in about forty seconds. Clean diff, green tests, looked fine. Then I hit a bug in that exact code, opened it back up — and had no idea why it was written that way. Which approach it rejected. What it assumed about the rest of the system. What it had quietly left for later. On paper the call was mine. I never actually made it.
That's not just me, and it isn't really about agents. A pull request records what changed — the diff. Not why: the alternative that got weighed and dropped, the thing the change takes for granted, the edge case punted to "later." That reasoning is alive while the work happens and gone the second it commits. git blame gives you who and when. Never intent.
Humans have always under-documented this — the gap isn't new. Agents just make it sharper, and more common. The share of code I didn't type myself keeps climbing, and each of those PRs shows up with its reasoning already deleted. Whoever approves it — or me, three weeks later — reconstructs a decision nobody quite made.
So I built a small thing to catch that reasoning at the one cheap moment it's still around: the instant the PR gets opened.
What it does
It's a Claude Code hook on PreToolUse. When the agent reaches for gh pr create — or a push/commit to your default branch — with no rationale block, the hook stops the command and hands the job back to the same agent, same session, the reasoning still in context. It doesn't write the block itself; it makes the model that did the work write it down, then re-runs the original command with the block attached.
Four parts:
- Decisions — what it chose.
- Trade-offs — what it gave up, on purpose.
- Assumptions — what it takes for granted about the rest of the system.
- Limitations — what it deliberately doesn't handle.
What it looks like when it fires
Roughly what you'll see — the ●/⎿ glyphs are just Claude Code's own output:
$ npx add-reasoning-to-prs
✔ PreToolUse hook installed → .claude/settings.json
# …later, the agent finishes the work and reaches for the PR…
● Bash(gh pr create --title "Key the scratchpad by repo + branch" …)
⎿ Blocked by add-reasoning-to-prs:
No rationale block on this PR. From THIS session, write a
backthread:why block — Decisions / Trade-offs / Assumptions /
Limitations. Skip any part you didn't deliberate, then re-run.
● Got it — capturing what we decided, then re-opening.
● Bash(gh pr create --title "Key the scratchpad by repo + branch"
--body "…<!-- backthread:why --> … <!-- /backthread:why -->")
⎿ ✔ PR opened, rationale attached
That's the whole trick: a PreToolUse deny that hands the guidance back in the reason field, and the same agent picks it up. The retry already carries the block, so on the second pass the hook sees a rationale is there and lets it straight through — no loop, nothing to babysit.
And the block it writes looks something like this — nobody types it; the agent just writes down what it already reasoned through in the session:
<!-- backthread:why -->
**Decisions**
- The scratchpad lives in the config dir keyed by repo + branch, not a
gitignored in-tree file — nothing to accidentally commit, and it
works across worktrees.
**Trade-offs**
- Guidance rides along with the deny even though the hook gets no
version signal — the reason carries it regardless, so older setups
degrade gracefully.
**Limitations**
- A browser-opened PR (no `gh pr create`) isn't intercepted — a known
v1 gap.
<!-- /backthread:why -->
Notice there's no Assumptions section — nothing was deliberated there, so it left it out. And that last Limitations line is the tool telling on itself about a gap it knows it has. I'll take that over a clean diff any day.
It won't make things up
A rationale you can't trust is worse than none — so the case I find most convincing is the boring one. Point the same hook at a routine dependency bump:
Bump eslint 9.10 → 9.12
Nothing was deliberated, so nothing gets written — no block, no markers, no "various minor improvements." That's the design, not a miss. Point it at any version bump and it stays quiet.
When there is real reasoning, a local self-critique pass strips any line that just restates the diff or invents a reason that wasn't in the session — "chose polling over webhooks because the vendor exposes no webhook API" survives; "improved robustness and maintainability" doesn't. I'd rather it emit nothing than a plausible-sounding decision the agent never made.
It all runs locally, on your own model subscription — no account, no server, nothing uploaded, your source never leaves the machine. Forward-only, too: it documents the change being made and never rewrites history you already have (on a direct push to the default branch the block goes into the commit message instead). One command:
npx add-reasoning-to-prs
What it isn't
- Not a review bot. It doesn't judge or gate the PR — it records the why.
- Not a diagram or a wiki. It writes prose into the PR you were already opening.
- Not a code reader. It works from the agent's session, not by reading or shipping your source.
- Not retroactive. Forward-only; it doesn't touch closed history.
What it can't do yet
- Claude Code only, for now. Cursor and Codex are next.
- Open a PR in the GitHub web UI instead of through
gh, and it won't fire. - Squash-merges and work spread across several sessions flatten some of the context it can draw on.
None of those are secrets — they're the known gaps. If one's a dealbreaker, the repo's the place to say so.
The commercial side, plainly
I'll be straight, because you'd find it anyway. This hook is the open-source front end of Backthread (backthread.dev) — a paid, hosted product that pools the same rationale across a whole team and its history and pushes it back to you before you go looking: the cross-repo, longitudinal stuff a single local hook structurally can't do. The hook needs none of that to be useful. I'm shipping it standalone because writing the why down at the moment of the change is worth doing whether or not you ever touch the hosted thing — and a local, auditable, MIT tool is the most honest way to show you what I think the missing layer is.
If you're merging agent PRs faster than you can reason about them — and I suspect you are, because I was — point it at one repo and read what your next PR writes about itself. If the block comes out empty, that's the tool being honest, not broken. Kick the tires: github.com/backthread/add-reasoning-to-prs. I'd genuinely like to hear where the four-part block gets it wrong.
TL;DR — the whole page in ten lines
- A pull request records what changed, not why — and as agents write more of the code, that missing reasoning is the expensive part.
add-reasoning-to-prsis a Claude CodePreToolUsehook: when the agent runsgh pr create(or pushes to your default branch), it makes the same agent write the rationale into the PR.- The block has four parts — Decisions, Trade-offs, Assumptions, Limitations — and it's forward-only; it never rewrites existing history.
- It won't make things up: the block stays empty when nothing was deliberated, and a local self-critique pass strips lines that just restate the diff.
- Everything runs locally on your own model subscription — no account, no server, nothing uploaded. MIT and free:
npx add-reasoning-to-prs. - Today it's Claude Code only (Cursor and Codex next); PRs opened in the web UI and work split across squash-merges or multiple sessions aren't fully covered.
- It's the open-source front end of Backthread — the paid, team-wide hosted version — but useful entirely on its own.
- Repo: github.com/backthread/add-reasoning-to-prs.
Backthread — architectural memory for AI-coded codebases. See how it works ↗