Removing the Co-Authored-By Signature in Claude Code

Two ways to remove the 'Co-Authored-By' / 'Generated with' lines Claude Code adds to commits and pull requests: settings.json attribution and a commit-msg hook.

9 min read
ibrahimsql
1,712 words

Removing the Co-Authored-By Signature in Claude Code

Commits produced with Claude Code show a line like this by default:

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

PR bodies get a line like this:

🤖 Generated with [Claude Code](https://claude.com/claude-code)

If you don't want this, or you also want to clean the "Generated with Claude Code" line from PR bodies, there are two methods. First check the current state:

git log -1 --format=full | grep -i -E "co-authored-by|generated with"

Method 1: settings.json attribution

Add the attribution block to ~/.claude/settings.json as empty strings. That way Claude Code won't add the co-author line to commit and PR text:

JSON
{
  "attribution": {
    "commit": "",
    "pr": ""
  }
}

These fields are separate strings in the official settings reference; emptying one doesn't turn the other off. Don't replace the existing JSON wholesale; merge the attribution object with the other settings. The user setting can be overridden at the project/local/managed layers; seeing the key in the file is not proof of the effective setting. includeCoAuthoredBy is the old name. On supporting versions, attribution.sessionUrl: false is also needed for the cloud/Remote Control session link.

Note: this setting only stops the line Claude Code adds. It doesn't stop signatures added by other tools (Copilot, Gemini CLI, Codex) - those need Method 2.

Check that the JSON parses; this is only a syntax check:

jq -e '.attribution | .commit == "" and .pr == ""' ~/.claude/settings.json

Copying the Same Key to Other Tools

Claude Code's attribution schema is not a common standard for Codex, Gemini, or Grok. First identify the installed client and version. If you can't document a supported key, don't give the impression it works with guessed settings like [git] commit_footer or append_coauthor. A git hook can be a tool-independent message filter, but it doesn't apply to the PR body or to past commits.

Method 2: Filtering the New Commit Message Narrowly

The Git commit-msg hook receives the file path of the message being created and can edit it. It can be skipped with --no-verify; it doesn't change already-made commits. The filter below removes only the known Claude attribution and session trailer; it doesn't touch human contributors' Co-Authored-By lines.

Bash
#!/bin/sh
set -eu
MSG_FILE="$1"
TMP_FILE=$(mktemp "${MSG_FILE}.XXXXXX")
trap 'rm -f "$TMP_FILE"' EXIT HUP INT TERM
 
awk '
{
  lower = tolower($0)
  if (lower ~ /^co-authored-by:[[:space:]]*claude[[:space:]]+<noreply@anthropic[.]com>[[:space:]]*$/) next
  if (lower ~ /^claude-session:/) next
  if ($0 ~ /^Generated with Claude Code[[:space:]]*$/) next
  lines[++n] = $0
}
END {
  while (n > 0 && lines[n] ~ /^[[:space:]]*$/) n--
  for (i = 1; i <= n; i++) print lines[i]
}' "$MSG_FILE" > "$TMP_FILE"
 
mv "$TMP_FILE" "$MSG_FILE"

The filter doesn't remove a "Generated with" phrase appearing inside a prose sentence, or a different human co-author. If your real tool produces a different bot name/attribution format, first inspect the exact line in a message fixture and add a narrow rule. On error the temp file is cleaned and the hook fails; rather than silently continuing with a missing message, it keeps the error visible.

Enable the hook:

git config --global core.hooksPath ~/.git-hooks
chmod +x ~/.git-hooks/commit-msg

This global setting shadows existing repository hook directories. First inspect where the effective value comes from with git config --show-origin --get-all core.hooksPath and don't lose existing checks. For a single repo only, git config core.hooksPath .githooks is more narrowly scoped; Git doesn't automatically enable a cloned hook.

Verify:

git config --global core.hooksPath
# ~/.git-hooks

Test the hook - with a fake message file:

Bash
cat > /tmp/test-msg <<'EOF'
feat: add new feature
 
Co-Authored-By: Claude <noreply@anthropic.com>
Generated with Claude Code
EOF
sh ~/.git-hooks/commit-msg /tmp/test-msg
cat /tmp/test-msg
# Only "feat: add new feature" is expected to remain

Method 2.5: PR Body Cleanup

The commit message hook doesn't touch the PR description, because the PR body goes to GitHub, not to git. To clean the line in the PR body, lean on two places:

# 1) Clean the body beforehand when opening the PR with the gh CLI
gh pr create --title "feat: x" --body "$(cat pr-body.md | grep -v 'Generated with Claude Code')"
# 2) Update the body of an open PR
gh pr edit 42 --body "$(gh pr view 42 --json body -q .body | grep -v 'Generated with Claude Code')"

If you use the GitHub UI you can clean the PR description by hand too. When the squash message is produced server-side, the local commit-msg hook doesn't run; also inspect the PR body and the proposed squash message.

Two Checks to Be Sure of the Filter

Add a real-person co-author and a DCO line to the fixture:

Co-Authored-By: Human Contributor <human@example.com>
Signed-off-by: Human Contributor <human@example.com>

These two should be preserved while the Claude line is deleted. Also run the hook twice on the same file and check that the second run doesn't change the content. Finally, see that the hook doesn't hide the error when the file can't be written. The form grep ... && exit 1 || echo OK can show the test command's own error as success too; check the message content and the command's exit status separately.

Which, When?

SituationRecommendation
Block only the signatures Claude Code producesMethod 1 is enough
Mixed multiple toolsUse the documented setting in each tool; add only verified bot lines to the hook
The "Generated with" line in the PR body should be cleaned tooMethod 2.5 (gh CLI or manual)
Cleaning signatures from old commitsRewrite repo history with git filter-repo; no hook needed
Both togetherOne prevents production, the other sweeps up escapees

PR Workflow Notes

  1. Add the hook to the repo template. Version it as .githooks/commit-msg, and put the git config core.hooksPath .githooks install line in the README. Every developer runs it once on first clone.
  2. Verify in CI. A narrow example for the last commit; if all PR commits are needed, also scan the real base/head range:
YAML
- name: No AI trailers
  run: |
    if git log -1 --format=%B | grep -Eiq '^Co-Authored-By:[[:space:]]*Claude[[:space:]]+<noreply@anthropic[.]com>[[:space:]]*$'; then
      echo "AI signature found; the hook may not have run" && exit 1
    fi
  1. Watch squash-merge behavior. During a GitHub squash merge the PR body is carried into the commit message; the hook doesn't kick in at that point because the message is produced on GitHub's side. So clean the line in the PR body too.
  2. Conflict with CLA / DCO bots. Some repos require a Signed-off-by: line; the hook doesn't delete it because it isn't in the filter. While sweeping your own lines, deliberately preserve Signed-off-by.
  3. Keep the template policy. Don't confuse the desired Co-Authored-By guidance for human contributors with AI attribution; change only the unnecessary bot default.
  4. Re-check after rebase. Don't assume the rebase runs commit-msg on every replayed commit. The Git path and options used can differ; inspect the resulting messages separately.

Cleaning Past Commits

Deleting signatures from already-pushed commits isn't done with a hook; you need to rewrite history:

Bash
# install filter-repo
brew install git-filter-repo
# or: pipx install git-filter-repo
 
# Remove only the known Claude co-author line; preserve human contributors
git filter-repo --message-callback '
    return b"\n".join(
        line for line in message.split(b"\n")
        if not line.lower().startswith(
            b"co-authored-by: claude <noreply@anthropic.com>"
        )
    )
'

This changes commit identities and those of descendant commits, can invalidate existing signatures, and affects PR/tag references. Rewriting published shared history just for a presentation preference is usually not worth it. If needed, proceed with team approval, a verified backup, and a separate fresh clone; coordinate the force-push impact with all consumers.

You can also add a pre-push hook to check the last N commits before pushing:

Bash
#!/bin/sh
# ~/.git-hooks/pre-push
-if git log origin/main..HEAD --format=%B | grep -Eiq 'co-authored-by|generated with claude'; then
+if git log origin/main..HEAD --format=%B | grep -Eiq '^[[:space:]]*(Co-Authored-By:[[:space:]]*Claude[[:space:]]+<noreply@anthropic[.]com>|Generated with Claude Code([[:space:]]|$)'; then
  echo "AI signature in commits to be sent to the remote." >&2
  exit 1
fi

Troubleshooting

SymptomProbable causeFix
Hook doesn't runcore.hooksPath undefinedgit config --global core.hooksPath ~/.git-hooks
Hook doesn't runFile not executablechmod +x ~/.git-hooks/commit-msg
Line still in the message--no-verify, a different hook path, or a server-side squashInspect the real call path and the produced message; -m doesn't solve these
sed: -i: invalidmacOS vs GNU sedUse sed -i '' -e ... or take the mktemp version
Encoding broken🤖 emoji encoding issueWrite the grep -v filter locale-independently: LC_ALL=C

To see that the hook really fires, add a temporary echo "hook ran" >> /tmp/hook.log line, make a commit, then check the log.

Cases to Test the Hook

Input lineExpected result
Co-Authored-By: Claude <noreply@anthropic.com>known bot line deleted
The same Claude line in lowercasethe filter matches case-insensitively
Someone else's Co-Authored-By linepreserved
Claude-Session: abc123line deleted
The message's only line is the Claude attributionan empty message remains; don't bypass Git's normal empty-message check
Signed-off-by: Me <...>preserved
The hook errorsthe commit stops; inspect the current message and fix the error

Don't delete someone else's contribution record just to clean attribution. This filter is not a security boundary that satisfies authorship/organization policy; CI and server-side merge paths need their own checks.

Attribution That Requires Policy

If the project requires disclosing AI assistance, emptying the setting or replacing the bot name with a human name doesn't remove that obligation. Write the wording the team accepts explicitly into attribution.commit and attribution.pr. Signed-off-by is the author's DCO declaration; it doesn't stand in for a tool-usage note. The final decision is made based on the produced commit message and PR description, not on the existence of the settings file.

---
Share this post:

What do you think?

React to show your appreciation