A developer is halfway through a logging change when Claude Code starts work on a failing payment test. Both changes sit in the same working tree.

Claude Code repairs the test, updates the payment handler, runs the suite, and prepares a commit. The commit also contains the developer’s unfinished logging edit. Nothing failed. The tests are green and the combined diff looks plausible.

The reviewer sees one agent commit and reads it as one unit of intent.

That assumption is wrong.

A diff tells you what changed between two states. It does not tell you who caused each change, which task authorised it, or whether Claude Code inspected every changed line. If the agent can commit whatever happens to be present at the end of a run, unrelated work can acquire the task’s approval by accident.

Claude Code diff ownership gate

start with an unambiguous baseline

The cleanest setup is one isolated worktree, branch, container, or ephemeral checkout per serious agent run. Claude Code starts from a known commit. No developer has an unfinished edit in that filesystem. Another agent cannot quietly add a file while the first agent is working.

Record the state before the first write:

run:
  id: payments-test-fix-20260731-01
  task: PAY-2317
baseline:
  commit: 4c18b2a
  branch: fix/payment-timeout-test
  working_tree: clean
  untracked_files: []
allowed_paths:
  - src/payments/handler.ts
  - tests/payments/timeout.test.ts
submission_policy:
  require_owned_diff: true
  block_unattributed_changes: true
  block_preexisting_changes: true

A clean tree is easy to understand, but teams do not always have one. Sometimes the task is to continue a developer’s unfinished change. That can work if the workflow saves the existing patch before Claude Code touches it.

Capture the starting commit, staged diff, unstaged diff, untracked file list, and file digests. At the end, compare the final state with that record. The pre-existing patch remains visible as someone else’s work unless the task explicitly transfers ownership to the agent.

An instruction such as “avoid unrelated changes” helps the model focus. It does not prove ownership. Once several sources have written into the same tree, the transcript cannot reliably reconstruct who changed a line. The evidence must come from repository state.

attribute the final patch at hunk level

Path allowlists catch obvious drift. They do not solve same-file collisions.

A developer may change logging inside handler.ts while Claude Code edits timeout handling in the same file. The path is allowed, yet only part of its diff belongs to the task. For shared or higher-risk repositories, classify changed hunks rather than stopping at filenames.

changed_hunks:
  - path: src/payments/handler.ts
    lines: 88-104
    owner: agent_run
    reason: add bounded timeout handling
    evidence:
      - tests/payments/timeout.test.ts::stops_after_budget
  - path: src/payments/handler.ts
    lines: 24-29
    owner: preexisting
    reason: developer logging change
    disposition: excluded_from_submission

The important field is disposition. Detecting an unrelated edit without removing it only documents the mistake.

Each submitted hunk should tie back to the task, the run that created it, and some review evidence. That evidence might be a focused test, a requirement, a reproduced bug, or an approved refactor step. A hunk with no task reason is not ready for a commit, even when the code looks harmless.

This is where a review packet earns its keep. The reviewer should see the baseline, owned changes, excluded work, commands, tests, and unresolved questions without replaying the whole session.

make generated files show their working

Lockfiles, generated clients, snapshots, schemas, and compiled assets often fall outside the obvious source paths. Broadly labelling them “generated” creates an easy route around the boundary.

Record the command, tool version, inputs, and expected outputs:

generated_change:
  command: npm run generate:payments
  tool_version: payments-schema-cli@3.8.1
  inputs:
    - schema/payments.yaml
  outputs:
    - src/generated/payments-client.ts
  reproduced_from_clean_baseline: true

If the file cannot be reproduced from the task’s source change, exclude it or stop. Timestamps and formatting style are weak evidence. A clean regeneration is much better.

Generated-file receipts also make review less tedious. The reviewer can inspect the source input and reproduce the output instead of treating a large machine-written diff as an article of faith.

stop when the repository moves underneath the run

Another process may update the branch after Claude Code records its baseline. A formatter may touch files in the background. A second agent may write into the same checkout. The final diff now mixes the task with concurrent activity.

Do not ask Claude Code to infer ownership from code style or timestamps. Stop, identify the external change, and move the task into a fresh isolated workspace. If the concurrent update affects the plan, inspect it and request approval again. I covered that related failure in expiring Claude Code plans when repository state changes.

The same rule applies to test evidence. If the candidate changes after the suite runs, the green result belongs to an older state. Bind the evidence to the exact commit and run the required checks again.

Isolation removes much of this ambiguity. It is cheaper than building clever attribution around a shared, moving tree. Use detailed ownership records for the remaining cases, not as an excuse to keep every agent in one checkout.

put ownership in the commit gate

Before Claude Code commits or opens a pull request, the workflow should answer:

  1. Did the run start from a recorded repository state?
  2. Does every submitted hunk have an owner and task reason?
  3. Are the agent’s changes inside the approved path and change budget?
  4. Did the workflow exclude pre-existing and concurrent work?
  5. Can the reviewer reproduce generated changes and test evidence?

If one answer is unknown, the patch is not ready.

A compact gate result might look like this:

submission_gate:
  baseline_verified: true
  agent_owned_hunks: 7
  preexisting_hunks: 1
  concurrent_hunks: 0
  generated_files:
    count: 1
    reproduction_command: npm run generate:payments
  excluded_changes:
    - src/payments/handler.ts:24-29
  unattributed_hunks: 0
  decision: ready_for_review

This control catches more than accidental commits. It limits a confused subagent, a compromised tool, or a runaway formatter. A task approved to repair payment timeout handling does not gain authority over logging because both changes happened to appear in one process.

The free Claude Code production checklist helps teams define task scope, allowed paths, evidence, stop conditions, rollback, and review ownership before an agent writes.

make the commit mean one thing

A useful agent commit should represent one approved task and one explainable source of intent. Green tests are necessary, but they cannot establish that boundary.

I wrote Claude Code: Building Production Agents That Actually Scale for engineers bringing Claude Code into shared repositories and team review. It covers isolated execution, task contracts, scoped permissions, MCP tools, evals, observability, review packets, rollback, and release gates. The Kindle edition is available on Amazon.

Diff ownership is also a security boundary. Once agents can call enterprise tools or handle protected data, every action needs a named authority and evidence trail. Securing Enterprise AI Agents covers bounded authority, identity, MCP governance, data controls, approval, and incident evidence. The Enterprise AI Agents in Production bundle connects the engineering and security models.

My rule is straightforward: Claude Code submits only the changes it can tie to the task, the run, and the recorded baseline. Everything else stays out.