[BUG] Stop hook advises rewriting published history: shallow clone + signed GitHub merge commit flagged as "Unverified"

Status Open
Reported on v2.1.235
Maintainer reply None cached
Activity 0 comments · opened Aug 26, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

~/.claude/stop-hook-git-check.sh repeatedly flagged a GitHub-created merge
commit on published main as Unverified, and advised rewriting it:

There are commit(s) on branch 'main' that GitHub will show as Unverified
(missing signature, or committer email is not noreply@anthropic.com):
2e96012 noreply@github.com
Please run 'git config user.email noreply@anthropic.com && git config user.name Claude',
then 'git commit --amend --no-edit --reset-author' for the tip commit, or
'git rebase --exec "git commit --amend --no-edit --reset-author" 2e960122^' for
earlier commits, then push.

That commit is hours old, already on origin/main, signed by GitHub's own key,
and shown as Verified on github.com. Following the advice would have rewritten
authorship (a human's) and ~15 commits of published history on a shared default
branch.

Two defects combine to produce it:

  1. :83 — the predicate flags any commit whose committer email is not

noreply@anthropic.com, regardless of signature. GitHub commits its
server-side merges as GitHub <noreply@github.com> and signs them. So on a
merge-commit workflow this matches every merged PR.
Confirmed the flagged commit is signed:
$ git cat-file commit 2e96012 | sed '/^$/q' | grep -c '^gpgsig'
1

  1. :77 — the scope check uses git rev-list HEAD --not --remotes, which is

unsound on a shallow clone: remote-tracking refs exist but their history is
truncated, so ancestors of origin/main are unreachable from any remote ref
and read as local-only.
$ test -f .git/shallow && echo shallow # shallow
$ git branch -r --contains 2e96012 | wc -l # 0
$ git rev-list HEAD --not --remotes --count # 1 <- a published commit

The in-file comments cite #69586, which fixed a sibling case (the origin/HEAD
fallback). It does not cover this one: origin/$current_branch resolves fine
here; it is the ref's contents that are cut.

What Should Happen?

The hook should not advise history rewriting for commits it cannot prove are
unpublished, and should not flag correctly-signed commits at all.

Specifically:

  1. Drop the committer-email disjunct at :83. The signature-header test alone is

what GitHub's Verified badge reflects; a commit signed by a different
legitimate key (GitHub's) is not unverified.

  1. Skip the block entirely when $(git rev-parse --git-dir)/shallow exists —

--not --remotes cannot answer "is this published?" there. Optionally fall
back to git ls-remote for an authoritative answer.

Verified both fixes locally: with the email disjunct removed, three GitHub merge
commits stop being flagged while a synthetic unsigned commit is still caught.
Independently, git fetch --unshallow alone silences the hook
(local-only 1 -> 0, and git branch -r --contains then reports origin/main).

Error Messages/Logs

Steps to Reproduce

  1. Start a Claude Code Remote session (clones are shallow by default).
  2. Work in a repo that uses merge commits, where PRs are merged server-side on

github.com so the merge commit is committed by GitHub <noreply@github.com>.

  1. Ensure the merge commit is an ancestor of origin/main but outside the

truncated history of the local remote-tracking ref
(git branch -r --contains <sha> returns nothing).

  1. End a turn so the stop hook runs.
  2. Observe the Unverified warning and the --reset-author / rebase advice for a

published, signed commit.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.235 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Terminal.app (macOS)

Additional Information

Claude Code Remote (web) session container
git 2.43.0
Harness-configured SSH signing: commit.gpgsign=true, gpg.format=ssh,
user.signingkey=/home/claude/.ssh/commit_signing_key.pub,
user.email=noreply@anthropic.com
Hook: ~/.claude/stop-hook-git-check.sh (shipped; lines :77 and :83)

Broader pattern worth auditing: CCR clones shallow by default, and tooling that
reasons about "is this published?" returns confident wrong negative answers
there rather than reporting that it cannot tell. A second instance in the same
session:

$ git diff --stat main...FETCH_HEAD
(prints nothing — no merge base in truncated history; reads as "no changes"
rather than "cannot compute")

A "cannot determine" path is nearly always safer than defaulting to "nothing
found", especially when the output drives destructive advice.

View original on GitHub ↗