Skip to content
Code Ownership

Attributing AI-Authored Commits in Git

Concrete git workflows for attributing agent-authored commits — Generated-By trailers, Co-authored-by lines for bots, signed commits, and the audit trail you'll wish you had during your first incident.

Intermediate 10 min read Updated May 2026

Git was built in 2005 to track human authorship. It has exactly two identity fields per commit (author and committer) plus an open-ended trailer block at the end of the message. That's the entire schema for "who did this." It was enough when commits came from one keyboard at a time. It is not enough when half your throughput comes from Copilot suggestions, Cursor agent runs, and Claude Code sessions.

The good news: you don't need a new VCS. Git's commit-trailer mechanism, the long-standing Co-authored-by convention, and a small amount of CI plumbing give you a forensically useful audit trail. The bad news: you have to actually adopt it. Most teams are still treating agent-authored commits as if they were human-authored, which means six months from now they cannot answer "what percentage of our production code was generated by which model" without a manual audit.

This article gives you the exact git commands, trailer formats, and CI snippets to fix that.

What Git Already Gives You

A git commit has three places to attribute work:

  1. Author / Committergit commit --author="Name <email>". The author is who wrote the change; the committer is who applied it. These are separate fields by design (think: a maintainer applying a patch from a contributor).
  2. Co-authored-by trailers — a convention popularised by GitHub. Multiple Co-authored-by: lines in the message footer get rendered as multiple avatars on the commit page. GitHub uses this for pair-programming, Live Share, and bot contributions.
  3. Arbitrary trailers — any Key: Value line in the message footer is parseable by git interpret-trailers. This is where structured agent metadata lives.

Combine all three and you have enough fields to reconstruct: who initiated the agent run, which model produced the change, who reviewed it, and what prompt was used. No external database required for the basic case.

The Generated-By Trailer Pattern

The most useful single trailer for agent attribution is Generated-By:. It's not a git built-in, but it's a strong convention popularised by GitHub Copilot's PR agent and adopted by Cursor, Claude Code, and Replit. The format:

Generated-By: <agent-name>/<version> (model: <model-id>; operator: <email>)

Example commit message produced by Cursor Agent:

feat(api): add idempotency key validation to /payments

Validate Idempotency-Key header on all POST requests; reject
duplicates within the 24h window using Redis SETNX.

Generated-By: cursor-agent/0.42 (model: claude-sonnet-4-5; operator: [email protected])
Co-authored-by: Cursor Agent <[email protected]>
Signed-off-by: Alice Chen <[email protected]>

Three pieces of attribution in seven lines:

  • The operator (Alice) signed off, meaning she takes responsibility
  • The agent (Cursor 0.42 with Claude Sonnet 4.5) gets a Co-authored-by avatar
  • The operational metadata (model, version, operator) is queryable

To produce this commit pattern from a git CLI agent wrapper:

git commit \
  -m "feat(api): add idempotency key validation to /payments" \
  -m "Validate Idempotency-Key header on all POST requests; reject duplicates within the 24h window using Redis SETNX." \
  --trailer "Generated-By: cursor-agent/0.42 (model: claude-sonnet-4-5; operator: $(git config user.email))" \
  --trailer "Co-authored-by: Cursor Agent <[email protected]>" \
  --signoff

The --trailer flag was added in git 2.32 (2021) and is the right way to add structured metadata. Don't concatenate it into the message body manually — --trailer runs through git interpret-trailers and ensures the trailer block is properly delimited.

Signed-off-by: The Operator's Acknowledgement

The DCO (Developer Certificate of Origin) Signed-off-by: line, popularised by the Linux kernel, was originally a legal acknowledgement that the contributor had the right to submit the code. For agent-authored commits, it doubles as the operator's acknowledgement — the human saying "I accept responsibility for what this agent produced."

Make it mandatory for any commit from an agent email domain. The git config:

# In your agent wrapper, force --signoff on every commit
git config --local commit.gpgsign true
git config --local format.signoff true

Or via a prepare-commit-msg hook in .git/hooks/prepare-commit-msg:

#!/bin/sh
# Reject any agent-authored commit without an operator Signed-off-by

COMMIT_MSG_FILE=$1
AUTHOR_EMAIL=$(git config user.email)

case "$AUTHOR_EMAIL" in
  *agent*|*bot*|*copilot*|*cursor*)
    if ! grep -q "^Signed-off-by:" "$COMMIT_MSG_FILE"; then
      echo "Agent-authored commits require Signed-off-by from a human operator" >&2
      exit 1
    fi
    ;;
esac

The hook fires on every commit from an *agent* or *bot* email and refuses to let the commit complete without an operator signoff. This is the cheapest possible accountability gate.

Co-authored-by for Bot Avatars

GitHub renders Co-authored-by: trailers as additional commit avatars and counts them in contribution stats. Use this to make agent contributions visually obvious in the commit view:

Co-authored-by: Copilot <198982749+Copilot[bot]@users.noreply.github.com>
Co-authored-by: Claude Code <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>

For the GitHub Copilot SWE agent specifically, the canonical commit identity is the bot account Copilot (numeric user-id 198982749) and its noreply email 198982749+Copilot[bot]@users.noreply.github.com is what GitHub uses to associate the commit with the bot's avatar and account. The shorter [email protected] form turns up in some legacy IDE-Copilot trailers but does not link to the SWE-agent bot account.

For an email to render as the recognised GitHub avatar (rather than a generic identicon) it has to map to a real GitHub account — either a verified email on a GitHub user, or the noreply address for that user-id. The Claude Code default ([email protected]) has no GitHub user-id prefix, so it may show in the commit history but won't always link to a profile; teams who want a per-agent dashboard should register a service account on their GitHub org and use that account's noreply email instead.

GitHub's contribution graph likewise only counts Co-authored-by commits toward an account's stats when the email is verified on that account. An arbitrary [email protected]-style address may render the trailer in the commit listing, but it will not link to a profile and will not appear in any contribution graph. To get the per-agent dashboard "for free," point Co-authored-by at a registered service account (e.g. [email protected]) whose email is verified on a GitHub user.

Querying Agent Commits

Once trailers exist, querying becomes a one-liner. Find every commit in the last quarter that was agent-generated:

git log --since="3 months ago" \
  --grep="Generated-By:" \
  --format="%h %an %ae %s"

Group by model:

git log --since="3 months ago" \
  --format="%b" \
  | grep -oP "model: [a-z0-9.-]+" \
  | sort | uniq -c | sort -rn

Output:

   312 model: claude-sonnet-4-5
   188 model: gpt-4o-2024-11
   142 model: claude-opus-4-7
    44 model: cursor-small-2026-04

Find every commit where an agent changed src/billing/:

git log --since="3 months ago" \
  --grep="Generated-By:" \
  --format="%h %s" \
  -- src/billing/

Find agent commits without a Signed-off-by (your accountability gap):

git log --grep="Generated-By:" --format="%H %s" \
  | while read hash subject; do
      if ! git show -s --format="%b" "$hash" | grep -q "Signed-off-by:"; then
        echo "GAP: $hash $subject"
      fi
    done

These are not exotic queries. They run against any git repo where the trailer convention is followed.

CI Enforcement: The Trailer Lint

A pre-merge check that rejects PRs containing agent commits without proper trailers:

# .github/workflows/agent-trailer-lint.yml
name: Agent Trailer Lint

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Validate agent commits
        run: |
          BASE_SHA="${{ github.event.pull_request.base.sha }}"
          HEAD_SHA="${{ github.event.pull_request.head.sha }}"

          FAILED=0

          for COMMIT in $(git rev-list "$BASE_SHA".."$HEAD_SHA"); do
            AUTHOR_EMAIL=$(git show -s --format="%ae" "$COMMIT")

            # Is this an agent-authored commit?
            case "$AUTHOR_EMAIL" in
              *agent*|*bot*|*copilot*|*cursor*|*claude-code*)
                BODY=$(git show -s --format="%b" "$COMMIT")

                if ! echo "$BODY" | grep -q "^Generated-By:"; then
                  echo "::error::$COMMIT missing Generated-By trailer"
                  FAILED=1
                fi

                if ! echo "$BODY" | grep -q "^Signed-off-by:"; then
                  echo "::error::$COMMIT missing Signed-off-by from operator"
                  FAILED=1
                fi
                ;;
            esac
          done

          exit $FAILED

The check runs in seconds, fails loudly, and doesn't require any external service. New agent commits without the right metadata cannot land. Old commits are grandfathered in (no retroactive enforcement) — the goal is to draw the line at today and keep the trail clean from here forward.

The Backfill Problem

If you adopt this pattern in month 12 of a project that started in month 1, you have 11 months of agent-authored commits with no trailers. The backfill is not technically possible — you can rewrite history with git filter-repo, but doing so on a shared branch breaks every developer's working copy and invalidates every existing CI cache.

The pragmatic answer: don't backfill. Instead, write a one-time audit script that infers agent authorship for historical commits using heuristics (commit author email, commit cadence, diff style — agent commits tend to touch many files in a single shot) and produces a CSV side-channel record. Store the CSV in the repo at audit/historical-agent-commits.csv. Treat it as the best-effort historical record. From the cutover date forward, the trailer is the source of truth.

What to Adopt This Week

  1. Add the prepare-commit-msg hook to your agent wrapper (or the agent's git config).
  2. Pick a Co-authored-by email for each agent your team uses; document them in docs/agent-identities.md.
  3. Drop the agent-trailer-lint.yml workflow into one repo as a pilot. Tune the email patterns to match your agents.
  4. Run the "agent commits without Signed-off-by" query against your last 90 days. The count is your current accountability gap. Track it weekly.

The mechanism is older than git itself (the DCO predates git). The discipline of using it for agent attribution is what's new.

This article is part of the Code Ownership knowledge series (6 articles) Browse all Code Ownership articles →
Related Use Case

AI Code Traceability — Your developers don't write the code

Nobody has control anymore. Leaders have visibility.

Explore Use Case →