DEV Community

Sam Chen
Sam Chen

Posted on

The Agent Diff Has No Parent SHA. Stop Merging It.

A parentless agent patch is not a change. It is a rumor about some other tree. I will not merge that rumor today.

Did the model even start from your current HEAD?

Parent SHA first

I review AI patches by parent SHA first. I read the patched code only after that. Most failures hide between two commits, not inside one file.

The agent branched from an unnamed commit. Your teammates pushed new commits onto main. And nobody recorded the original base SHA.

Now the diff applies with hunks of fiction. Does this sequence already sound familiar to you?

How to read this catalog

Each anti-pattern below has three named parts. I list symptoms, root cause, and a runnable replacement. I keep every replacement small on purpose.

You should keep those replacements small too.

Anti-pattern 1: The orphan patch

The pull request shows a diff without a parent. git apply works Monday and then fails Tuesday. The chat says done and dumps files, not commits.

Symptoms

  • The pull request never names a recorded base_sha.
  • The same patch fails after an unrelated main push.
  • The agent returns file blobs instead of commits.

Root cause

The agent never serialized its real starting tree. You treated the chat blob as history. Git still disagrees with that entire chat story.

Replacement

Record the parent SHA before the first prompt. Refuse any patch that omits that SHA.

# example: persist the only parent that matters
git rev-parse HEAD > .agent-base-sha
git status --porcelain > .agent-dirty-index
Enter fullscreen mode Exit fullscreen mode

Would you accept a human PR with no merge base?

Anti-pattern 2: The dirty overlay

Local edits sit unstaged while the agent writes files. The same path exists in your tree and the patch. Tests pass because your leftovers filled the holes.

Symptoms

  • Unstaged local files share paths with the agent patch.
  • Tests pass only with your leftover working tree.
  • git status was never clean at agent start.

Root cause

Two authors shared one working tree by accident. Neither person truly owned that shared tree. The agent overlayed a dirty index without checking.

Replacement

Start the agent on a clean worktree. Use a second clone if the tree is busy.

# example: refuse to start on a dirty tree
if [ -n "$(git status --porcelain)" ]; then
  echo "dirty tree; refuse agent start"
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Is that extra clone disposable on purpose then? Keep that clone disposable after every agent run.

Anti-pattern 3: The silent rebase

The patch applies with fuzz and with offset. Reviewers read a diff that HEAD will never see. CI on main fails after a "successful" agent run.

Symptoms

  • git apply reports offsets you did not review.
  • The reviewed hunks never match git show HEAD.
  • Main CI fails after the agent session looked green.

Root cause

HEAD moved while the agent was writing. Nobody replayed the patch onto current main. You reviewed a ghost of yesterday's tree.

Replacement

Rebase the agent commit onto current HEAD. Review that replayed diff with fresh eyes. Ship only that replayed commit after review.

# example: replay onto today's origin/main
BASE=$(cat .agent-base-sha)
git fetch origin
git rebase --onto origin/main "$BASE"
Enter fullscreen mode Exit fullscreen mode

If the rebase conflicts, the original review is void. Run the review again on the result.

Anti-pattern 4: Generated-file amnesia

The source changed and the lockfile stayed put. Generated protobuf stubs lag behind the .proto edit. CI generates files the agent never opened.

Symptoms

  • Source imports a package the lockfile does not pin.
  • Build artifacts differ between agent host and CI.
  • Reviewers never opened the generated file diff.

Root cause

The agent patched only the file it was shown. The build graph stayed invisible to the session. You merged half of a real change.

Replacement

List generated outputs inside the receipt file. Diff those outputs after the local replay.

# example receipt fragment, not a live config
must_regenerate:
  - package-lock.json
  - src/generated/
commands:
  - npm install --package-lock-only
  - make proto
Enter fullscreen mode Exit fullscreen mode

Did you read the generated diff at all? If not, you skipped the real change.

Anti-pattern 5: Remote-only green

The agent host printed a cheerful all-tests-passed line. Your laptop cannot even find that command. No artifact ever left the remote session.

Symptoms

  • Success exists only as a sentence in chat.
  • The command line is not checked into the receipt.
  • Local replay is skipped because the host looked green.

Root cause

That success lived on another machine entirely. You imported the story, not the bytes. A free remote host makes that swap easy.

I sometimes point the first pass at MonkeyCode. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

MonkeyCode offers free model access and a free server option. That host is fine for a first pass. It is not my merge oracle though.

Copy the command list off the host. Run every command on your local checkout. Store the exit codes beside both SHAs.

A green remote log without a local receipt is theater. Do not import that theater into main.

The artifact: a two-SHA receipt

I want two SHAs sitting on disk. I want the command transcript beside them.

Here is an example script for the receipt. Treat it as a proposal, not a benchmark.

#!/usr/bin/env python3
"""Example only: write a two-SHA agent receipt."""
from __future__ import annotations

import json
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path


def sh(*args: str) -> str:
    proc = subprocess.run(args, check=True, capture_output=True, text=True)
    return proc.stdout.strip()


def main() -> int:
    if len(sys.argv) != 2:
        print("usage: receipt.py write|verify", file=sys.stderr)
        return 2

    root = Path(".agent-receipt.json")
    mode = sys.argv[1]
    head = sh("git", "rev-parse", "HEAD")
    branch = sh("git", "rev-parse", "--abbrev-ref", "HEAD")
    dirty = sh("git", "status", "--porcelain")

    if mode == "write":
        payload = {
            "recorded_at": datetime.now(timezone.utc).isoformat(),
            "base_sha": head,
            "branch": branch,
            "dirty": bool(dirty),
            "dirty_paths": dirty.splitlines(),
            "result_sha": None,
            "commands": [],
        }
        root.write_text(json.dumps(payload, indent=2) + "\n")
        print(f"wrote base_sha={head}")
        return 0 if not dirty else 1

    if mode == "verify":
        data = json.loads(root.read_text())
        base = data["base_sha"]
        merge_base = sh("git", "merge-base", base, head)
        if merge_base != base:
            print(f"base {base} is not an ancestor of {head}")
            return 1
        if data.get("dirty"):
            print("receipt started dirty; refuse verify")
            return 1
        data["result_sha"] = head
        data["verified_at"] = datetime.now(timezone.utc).isoformat()
        root.write_text(json.dumps(data, indent=2) + "\n")
        print(f"ok base={base} result={head}")
        return 0

    print("unknown mode", file=sys.stderr)
    return 2


if __name__ == "__main__":
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode

How I run the example

This run sequence is a labeled example. Change the names to match your repo.

# 1. freeze the parent on a clean tree
python3 receipt.py write || exit 1

# 2. let the agent work, then commit
# git add -A && git commit -m "agent: named change"

# 3. prove the parent still ancestors HEAD
python3 receipt.py verify || exit 1

# 4. rebase if origin/main moved under you
git fetch origin
git rebase origin/main
python3 receipt.py verify
Enter fullscreen mode Exit fullscreen mode

No receipt means no review from me. That is the whole review rule here.

Test plan for the receipt

Do not trust this example script blindly. Break it on purpose before any agent run.

  1. Run write on a dirty tree and expect exit 1.
  2. Commit a dummy file, then run verify for exit 0.
  3. Reset --hard to HEAD~1 and expect verify to fail.
  4. Rebase onto an unrelated commit and expect ancestor failure.
  5. Delete .agent-receipt.json and confirm the review stops.

If step 3 still prints ok, the script is lying. Fix it before any real agent run.

Decision table

Use this table during review, not during prompting. When a row says no, stop merging.

Signal Trust the patch? Next move
No base_sha on disk No Restart the agent on a named SHA
Dirty tree at write No Clean clone, then run write again
merge-base is not base_sha No Rebase, then re-review the new diff
Remote tests green only No Replay the same commands locally
Generated files missing from the diff No Run generators, then diff outputs
Two SHAs plus a clean local replay Candidate only Human review of the replayed diff

Notice the last row still says only candidate. People still need to read the replayed diff.

What this does not catch

A parent SHA is not a behavior specification. It does not prove runtime behavior by itself. The example script ignores several real failure classes.

  • Flaky tests that passed once on the remote host.
  • Secrets that existed only in the remote environment.
  • Prompt text that never entered the git history.
  • Semantic bugs that still compile and link cleanly.

I still read the replayed diff slowly. Do you actually read that replayed diff?

Who should not use this

Skip this workflow if you have no local clone. Skip it if hermetic CI is already your only trusted runner.

Skip it for throwaway spikes you will delete before lunch. Also skip it if you cannot run the project's tests locally.

A receipt without a replay is another pretty story. Remote-only teams need a named CI SHA.

Closing

The model will keep returning patches anyway today. HEAD will keep moving under those patches. Those two facts do not care about your deadline.

I want a parent SHA, a result SHA, and a local replay. Everything else is narration from a model.

If you draft on a free remote host, steal this receipt script. Make the host prove its parent SHA. Then decide whether the replayed diff is mergeable.

Top comments (0)