<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muhannad Salkini</title>
    <description>The latest articles on DEV Community by Muhannad Salkini (@muhannad_salkini_1036ea1b).</description>
    <link>https://dev.to/muhannad_salkini_1036ea1b</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4155630%2Fc57bb0c5-bb78-4295-90b8-2f42bede1886.png</url>
      <title>DEV Community: Muhannad Salkini</title>
      <link>https://dev.to/muhannad_salkini_1036ea1b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muhannad_salkini_1036ea1b"/>
    <language>en</language>
    <item>
      <title>How to review pull requests written by coding agents</title>
      <dc:creator>Muhannad Salkini</dc:creator>
      <pubDate>Fri, 16 Oct 2026 17:23:41 +0000</pubDate>
      <link>https://dev.to/muhannad_salkini_1036ea1b/how-to-review-pull-requests-written-by-coding-agents-12e8</link>
      <guid>https://dev.to/muhannad_salkini_1036ea1b/how-to-review-pull-requests-written-by-coding-agents-12e8</guid>
      <description>&lt;p&gt;A pull request from a coding agent usually looks better than one from a tired human. The description is complete, the code is formatted, the tests pass, and every changed line has a reason. That polish is exactly what makes it hard to review. The usual signals that something is off (a messy diff, a vague description, a missing test) aren't there.&lt;/p&gt;

&lt;p&gt;The mistakes agents make are different from the ones people make. They rarely forget a semicolon. They often solve the problem in a way your team decided against, or change something that other code relies on without noticing the other code exists. Both of those are invisible if you only read the diff.&lt;/p&gt;

&lt;p&gt;Here is the checklist we use.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Read the description last
&lt;/h2&gt;

&lt;p&gt;Agents write confident summaries of what they intended to do. Read the diff first and form your own view of what changed, then compare it with the description. Gaps between the two are where the bugs are: "refactored the retry logic" that also changed the timeout default, "added a field" that also made an existing one optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Ask who depends on what changed
&lt;/h2&gt;

&lt;p&gt;For every function, type or exported value the PR changes, find the callers. Not the ones in the diff, all of them.&lt;/p&gt;

&lt;p&gt;An agent asked to "add a required &lt;code&gt;orgId&lt;/code&gt; parameter to &lt;code&gt;loadSettings&lt;/code&gt;" will update the function and the callers it found. Whether it found all of them depends on how it searched. Your editor's "find references" or &lt;code&gt;grep&lt;/code&gt; takes ten seconds and is the highest-value thing you can do on an agent PR.&lt;/p&gt;

&lt;p&gt;Be suspicious when a tool says nothing calls a function. In our own measurements, a code graph reported zero callers, labelled "exact", for 17 of 26 functions that had real callers. "No results" from a tool means "unknown", not "safe".&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Check it against what the team already decided
&lt;/h2&gt;

&lt;p&gt;Every codebase has rules that aren't enforced by the compiler: all database access goes through the repository layer, prompts live in files and not inline strings, model calls go through one shared client, no new dependencies without discussion. People learn these from review comments. Agents don't, unless they're written down somewhere the agent reads.&lt;/p&gt;

&lt;p&gt;When you review, ask: does this follow how we do things here, or just a way that works? Agent PRs drift toward the most common pattern on the internet, not the one your team picked.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Look for scope creep
&lt;/h2&gt;

&lt;p&gt;Agents like to tidy. A PR to fix one bug touches three unrelated files because the agent "noticed" something. Each tidy-up is a small, unrequested change that nobody asked to review. Ask for them to be split out, or reverted.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Check the tests test the change
&lt;/h2&gt;

&lt;p&gt;Agents are good at making tests pass, and occasionally that means changing the test. Look at test diffs as carefully as code diffs: a loosened assertion, a new &lt;code&gt;skip&lt;/code&gt;, a mock that now returns whatever the code expects. A green check on a PR that edited its own tests means less than it seems.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Check error paths, not just the happy path
&lt;/h2&gt;

&lt;p&gt;Agent code tends to handle the case it was asked about. Look at what happens on the failure branches: swallowed errors, a &lt;code&gt;catch&lt;/code&gt; that logs and continues, a fallback that silently returns an empty list. These pass every test that only exercises the success case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move the checks earlier
&lt;/h2&gt;

&lt;p&gt;Most of this checklist is the reviewer doing work the agent could have done before opening the PR. You can move a lot of it to write time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write your decisions down where the agent reads them.&lt;/strong&gt; &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;.cursor/rules&lt;/code&gt; or &lt;code&gt;AGENTS.md&lt;/code&gt;, with the reason for each rule, not just the rule. Agents follow rules they can see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make "find all callers" part of the task.&lt;/strong&gt; Tell the agent to list every caller of anything whose signature it changes, and to update or explain each one. It works noticeably better than hoping it does it on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep PRs small.&lt;/strong&gt; One concern per PR. Agents will happily do five things at once if you let them, and five things are much harder to review than one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Automate the two hard parts
&lt;/h2&gt;

&lt;p&gt;Steps 2 and 3 are the ones that don't scale by hand. Finding every caller of every changed symbol, and remembering every decision the team has made, is a lot to ask of a reviewer on their tenth agent PR of the day.&lt;/p&gt;

&lt;p&gt;That is what we built &lt;a href="https://tently.dev" rel="noopener noreferrer"&gt;Tently&lt;/a&gt; for. It is an AI code reviewer for GitHub that checks each pull request against the code that depends on it and against your team's decisions, which it learns from merged pull requests, review threads and docs. Findings land on the PR with evidence: the affected callers, or the decision and where it came from. It runs next to whatever reviewer you already use.&lt;/p&gt;

&lt;p&gt;The same decisions are available while the agent writes, through an open-source MCP server for Claude Code, Cursor and Codex (&lt;a href="https://github.com/tently-team/tently-mcp" rel="noopener noreferrer"&gt;tently-team/tently-mcp&lt;/a&gt;), so fewer problems reach the PR at all.&lt;/p&gt;

&lt;p&gt;With or without a tool, the checklist holds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the diff before the description.&lt;/li&gt;
&lt;li&gt;Find every caller of what changed.&lt;/li&gt;
&lt;li&gt;Check it against your team's decisions.&lt;/li&gt;
&lt;li&gt;Push back on scope creep.&lt;/li&gt;
&lt;li&gt;Review test changes as carefully as code.&lt;/li&gt;
&lt;li&gt;Read the error paths.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>codereview</category>
      <category>productivity</category>
      <category>github</category>
    </item>
    <item>
      <title>What diff-only AI code review misses: regressions outside the diff and decision drift</title>
      <dc:creator>Muhannad Salkini</dc:creator>
      <pubDate>Mon, 12 Oct 2026 07:21:15 +0000</pubDate>
      <link>https://dev.to/muhannad_salkini_1036ea1b/what-diff-only-ai-code-review-misses-regressions-outside-the-diff-and-decision-drift-535j</link>
      <guid>https://dev.to/muhannad_salkini_1036ea1b/what-diff-only-ai-code-review-misses-regressions-outside-the-diff-and-decision-drift-535j</guid>
      <description>&lt;p&gt;Most AI code reviewers work the same way. They read the pull request diff, maybe a few files around it, and comment on what they see: a missing null check, an unhandled promise, a SQL string built by hand. They are good at this now, and if your team uses one, keep it.&lt;/p&gt;

&lt;p&gt;But a whole class of bugs is invisible to that approach. The diff looks fine. The problem is somewhere the reviewer never looks.&lt;/p&gt;

&lt;p&gt;They come in two kinds.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Regressions outside the diff
&lt;/h2&gt;

&lt;p&gt;A function gets a new required parameter. The pull request updates the function and the two callers the author remembered. There are three more callers in other packages. They are not part of the diff, so the reviewer never reads them, the type checker doesn't help if one of them goes through an &lt;code&gt;any&lt;/code&gt;-typed adapter, and tests pass if nobody tests that path.&lt;/p&gt;

&lt;p&gt;The diff is correct. The change is not.&lt;/p&gt;

&lt;p&gt;This gets worse with coding agents. An agent changing a function rarely goes looking for every caller unless you tell it to, and the pull request it opens is usually tidy and confident. A reviewer that reads only that pull request has nothing to object to.&lt;/p&gt;

&lt;p&gt;To catch this, the reviewer needs one question answered before it reads the diff: &lt;strong&gt;who depends on the code that changed?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Decision drift
&lt;/h2&gt;

&lt;p&gt;The second kind is quieter. Say your team decided, months ago and on purpose, that every LLM call goes through one shared client, so cost tracking and retries live in one place. The decision is in a merged pull request, a review thread and a line in &lt;code&gt;CLAUDE.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A new pull request calls the provider SDK directly from a new file. It works. It has tests. It is also the start of the thing you decided not to do, and nothing in the diff says so.&lt;/p&gt;

&lt;p&gt;A generic reviewer can't flag this, because the rule isn't a generic rule. It's yours. It lives in your history, not in the code being changed.&lt;/p&gt;

&lt;p&gt;To catch this, the reviewer needs a second question answered: &lt;strong&gt;which of our decisions apply to the files that changed?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Answering "who depends on this?" is harder than it looks
&lt;/h2&gt;

&lt;p&gt;The obvious tool for the first question is a code graph: index the repository, then ask for the callers of each changed symbol. We built our regression check on one, and then we measured it, because a missed caller is a missed regression.&lt;/p&gt;

&lt;p&gt;We labelled ground truth with the TypeScript LanguageService (every real call site of a target function, attributed to the function that contains it) and compared a code graph's answers against it.&lt;/p&gt;

&lt;p&gt;On our own TypeScript monorepo, with an off-the-shelf graph tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;57.4% recall&lt;/strong&gt;: it found 27 of 47 real callers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;17 of 26 functions came back with zero callers&lt;/strong&gt;, even though every one of them had real ones.&lt;/li&gt;
&lt;li&gt;Some misses were same-file calls, not just cross-package ones.&lt;/li&gt;
&lt;li&gt;Every empty result was reported as exact, with risk LOW.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the one that matters. A tool that says "I don't know" is fine. A tool that says "nothing calls this, safe to change" when three things call it is how regressions get approved.&lt;/p&gt;

&lt;p&gt;We ran the same kind of study on &lt;a href="https://github.com/vitejs/vite" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; with a different engine. It named the exact caller 67.9% of the time, but every one of the 185 call sites fell inside some caller it reported. The engine was naming the wrong enclosing function, not missing the call. That is a much better failure, because a reviewer reading the reported callers still finds the call.&lt;/p&gt;

&lt;p&gt;What we took from this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An empty caller list is not evidence of safety.&lt;/strong&gt; Treat it as "unknown", search the code anyway, and read likely consumers before deciding a change has no impact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure your graph against a real oracle&lt;/strong&gt;, on a real repository. A small clean fixture will read 100% and tell you nothing. The misses only show up at scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pass the reviewer the uncertainty, not just the answer.&lt;/strong&gt; Our impact results carry an explicit "approximate" label and a list of reasons they may be incomplete (dynamic dispatch, re-exports, reflective calls), and the reviewing model sees those.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Answering "which decisions apply?" needs a decision store
&lt;/h2&gt;

&lt;p&gt;Decisions are scattered: merged pull requests, review threads where someone said "please don't do this, we use X", ADRs, READMEs, &lt;code&gt;CLAUDE.md&lt;/code&gt;. To check a pull request against them you need them in one place, each with a scope (the paths it covers) and a link to where it came from.&lt;/p&gt;

&lt;p&gt;We mine candidate decisions from that history automatically, but a mined decision is a guess. So they start as proposals, and someone on the team approves, edits or rejects each one. A finding that cites an approved decision can block a merge. One that cites an unconfirmed proposal is labelled as such and weighted lower.&lt;/p&gt;

&lt;p&gt;The link back to the source matters more than we expected. "This breaks a decision" is easy to dismiss. "This breaks the decision from that pull request six months ago, here is the thread" is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the noise down
&lt;/h2&gt;

&lt;p&gt;Both checks are easy to make noisy. A reviewer that flags every caller of every changed function is useless within a week. What worked for us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two passes.&lt;/strong&gt; A cheap model sorts candidate findings, then a stronger model has to confirm each one against the actual code before anything is posted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence on every finding.&lt;/strong&gt; The affected callers by name and file, or the decision and its source. No evidence, no comment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback that sticks.&lt;/strong&gt; A finding the team dismisses is not posted again on the next push.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it next to your current reviewer, not instead of it
&lt;/h2&gt;

&lt;p&gt;Diff-level review and context-level review catch different bugs. We run both. The diff reviewer catches the null check. The context check catches the caller in the other package and the decision from six months ago.&lt;/p&gt;

&lt;p&gt;We built this into &lt;a href="https://tently.dev" rel="noopener noreferrer"&gt;Tently&lt;/a&gt;, an AI code reviewer for GitHub pull requests that checks every change against the code that depends on it and against your team's decisions. The same decision store is also available while the agent writes code, through an open-source MCP server (&lt;a href="https://github.com/tently-team/tently-mcp" rel="noopener noreferrer"&gt;tently-team/tently-mcp&lt;/a&gt;), so Claude Code, Cursor or Codex can look up the rules before the pull request exists.&lt;/p&gt;

&lt;p&gt;Whatever you use, the two questions are worth asking of any pull request, human or agent-written:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Who depends on the code that changed?&lt;/li&gt;
&lt;li&gt;Which of our decisions apply to the files that changed?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your reviewer can't answer them, someone on your team is answering them by memory.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codequality</category>
      <category>llm</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
