· 5 min read ·

When an Agent Writes the Code, Git Blame Only Gets You Halfway

Source: simonwillison

When a coding agent writes a line of code, pointing git blame at it tells you who ran the agent, not who made the decision. The traditional compact around blame assumes that author and decision-maker are the same person. They are not, when the author is a model running in a session you initiated.

Simon Willison’s guide on using Git with coding agents covers the session-level workflow well: start from a clean working tree, commit frequently, use worktrees for parallel sessions, review diffs before staging. What the guide points toward but does not dwell on is the longer-term auditability problem. When an agent contributes a significant fraction of a codebase, the question “why does this code exist?” becomes harder to answer, and the question “should I scrutinize this more carefully than code a colleague wrote?” becomes more urgent. The session discipline solves recoverability. It does not, on its own, solve legibility across time.

What Attribution Actually Requires

The minimum useful record for an AI-generated commit has three components: who ran the agent, which model produced the output, and what the original task was. Standard commit author fields capture the first. Most tools handle the second differently.

Aider tags every commit with the model name and a description, producing a history that reads like (aider) fix: update token expiry check in session.py. The model attribution is automatic and queryable: git log --grep="aider:" filters to AI-authored commits instantly. Claude Code does not auto-commit, leaving attribution to you.

The git community settled on Co-Authored-By trailers for shared authorship, and they work here too:

git commit -m "Add token refresh with exponential backoff

Agent task: Handle 429 responses from the token endpoint with
exponential backoff, jitter, max 32s cap, abort after 5 attempts.

Co-Authored-By: Claude <noreply@anthropic.com>"

GitHub renders these trailers in the commit view and associates them with contribution graphs. More practically, the trailer makes the commit filterable: git log --grep="Co-Authored-By: Claude" returns only AI-assisted commits across the full history.

For teams with compliance requirements around code origin, this filter matters. It is the mechanism for answering “how much of this system was AI-generated?” during an audit. Building it into your commit practice from the start costs nothing. Retrofitting it later, when someone asks, costs considerably more.

The Task Description Belongs in the Commit Body

The most underused practice is storing the original task description in the commit body. The subject line captures what was done. The body captures why it was requested, which is the information that git blame historically extracted from commit context but cannot reconstruct from an agent session.

git commit -m "Refactor session validation to use constant-time comparison

Agent task: The session token comparison in validate_session() is
vulnerable to timing attacks because it uses string equality. Replace
with hmac.compare_digest() or equivalent constant-time comparison.
Security review flagged this in the March audit.

Co-Authored-By: Claude <noreply@anthropic.com>"

Months later, when a developer is debugging a performance regression in validate_session(), the commit body explains that the constant-time comparison was deliberate. Without it, the implementation looks like an odd choice, and someone may “fix” it back to string equality.

Willison frames this as context for future agent sessions, and that is correct. But the first audience is the human maintainer who inherits this code with no record of the conversation that produced it. Writing the original task into the commit body serves both audiences at once.

Pre-Commit Hooks as the Automated Safety Gate

The session-level review, committing before you invoke and reviewing the diff after, catches obvious problems. Pre-commit hooks run automatically on every commit and catch the class of problems that survive human review: type errors, linting violations, accidentally committed secrets.

For a TypeScript project, a minimal hook setup using pre-commit:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/awslabs/git-secrets
    rev: 1.3.0
    hooks:
      - id: git-secrets
  - repo: local
    hooks:
      - id: typecheck
        name: TypeScript typecheck
        entry: npx tsc --noEmit
        language: system
        pass_filenames: false
      - id: lint
        name: ESLint
        entry: npx eslint --max-warnings=0
        language: system
        types: [javascript, ts]

These hooks run on every commit regardless of whether a human or an agent staged the changes. They are particularly useful for agent-generated commits because agents optimize for test passage and may not run the linter as part of their internal loop. A session that produces code passing all tests but containing a type error is not uncommon; the hook catches it before it reaches history.

There is a specific failure mode worth knowing. When a pre-commit hook rejects a commit, the commit does not happen. If you then fix the issue and want to commit again, create a new commit rather than amending the previous one. Amending after a hook failure would modify the last successful commit, not retry the failed one, which can silently destroy work. This edge case is consequential enough that Claude Code’s safety protocol calls it out explicitly: after a hook failure, fix the issue, re-stage, and create a new commit.

Branch Protection as the Outer Defense

Session-level practices operate in your local environment. For any repository where an agent might eventually push to a remote, branch protection provides the structural guarantee that no agent-authored commit reaches main without human review.

Standard configuration covers the obvious cases: require pull requests before merging, require at least one human approval, require CI and static analysis to pass, block force pushes unconditionally. Beyond those basics, CODEOWNERS adds path-level review requirements:

# CODEOWNERS
/src/auth/     @security-team
/src/payments/ @payments-team
/.github/      @devops-team

When an agent touches an auth module or a CI configuration, the CODEOWNERS file ensures someone with domain context sees the change before it lands. The agent may have produced correct code; the purpose of CODEOWNERS is not to assume otherwise, but to route the diff to the right reviewer automatically.

A convention worth adopting alongside this: agents should open draft pull requests, not ready-to-merge ones. Draft status signals that human review is required and prevents accidental merges during a CI green run. Some teams go further and require a specific label, set by a human, before a PR authored by an agent session is eligible to merge.

The Two Layers Are Separate Concerns

The session workflow and the infrastructure layer solve different problems. The session workflow, covered thoroughly in Willison’s guide and common practice among people who use coding agents regularly, makes individual sessions auditable and recoverable. The infrastructure layer, hooks and branch protection and attribution conventions, ensures that the repository’s safety guarantees hold regardless of what happened in the session.

The infrastructure layer requires setup once. After that, it runs automatically for every subsequent session and every future contributor. Given how much ground a coding agent covers in a single invocation, the automated layer is load-bearing, not optional. Setting it up before you start working with agents, rather than after the first incident that makes you wish you had it, is the right order of operations.

Was this interesting?