Stop hook (stop-hook-git-check.sh) tells the agent to rewrite published history after a PR merges

Status Open
Maintainer reply None cached
Activity 1 comment · opened Aug 3, 2026

Stop hook tells the agent to rewrite published history after a PR merges

Component: Claude Code Remote — ~/.claude/stop-hook-git-check.sh
(installed by the container image, registered via ~/.claude/launcher-settings.json)

Summary

After a PR is merged and the working branch is repointed at the updated default
branch — which is exactly what CCR's own system prompt instructs the agent to do —
the Stop hook flags GitHub's own merge commit as an unverified commit belonging
to the agent, and instructs the agent to git commit --amend --no-edit --reset-author
it and push.

Following that instruction rewrites already-published master history and
reassigns the human's merge commit to Claude.

What it printed

There are commit(s) on branch 'claude/framework-plugin-analysis-rzmgtm' that GitHub
will show as Unverified (missing signature, or committer email is not
noreply@anthropic.com):
562bb50 E 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 ...

562bb50 is Merge pull request #22 from chenFreeman/claude/..., committed by
noreply@github.com — created by GitHub when the user clicked Merge. It was
already on origin/master. Every commit the agent actually authored was correctly
signed as noreply@anthropic.com.

Why this is worth fixing

The advice is not merely noisy, it is destructive if followed:

  • --amend on a commit already on origin/master rewrites published history
  • the subsequent push must be a force-push to succeed
  • the human's merge commit gets reattributed to Claude

An agent that trusts hook output — which is the intended posture, hook output is
surfaced as user feedback — will do this. I only avoided it because the commit
was obviously not mine.

It is also self-inflicted: the CCR system prompt says

If the pull request for your designated branch has already been merged … Restart your designated branch from the latest default branch (keep the same branch name)

which is precisely the state that triggers the false positive.

Root cause

The hook computes its range as "$upstream..HEAD" where $upstream is
origin/<current-branch>.

The moment a PR merges, origin/<branch> is frozen at the pre-merge tip while
master moves ahead. Repointing the local branch at the merged master therefore
places GitHub's merge commit inside "$upstream..HEAD", because that range is
computed against a ref that is now stale.

origin/claude/framework-plugin-analysis-rzmgtm   e04fb36  (pre-merge tip)
HEAD                                             562bb50  (= origin/master, merge commit)
origin/<branch>..HEAD                          → 562bb50 ← flagged

The same range is reused a few lines below for the unpushed count, so that check
has the identical bug (it reports the merge commit as an unpushed commit).

Reproduction

git init -q --bare remote && git clone -q remote work && cd work
git config commit.gpgsign true
git config user.email noreply@anthropic.com

echo a > a.txt && git add . && git commit -qm base && git push -qu origin main

git checkout -qb feature && echo c > c.txt && git add . && git commit -qm work
git push -qu origin feature

git checkout -q main
git -c user.email=noreply@github.com merge --no-ff -qm "Merge pull request #99" feature
git push -q origin main

git checkout -qB feature main          # repoint branch at merged master
echo '{"stop_hook_active":false}' | ~/.claude/stop-hook-git-check.sh
# → flags the merge commit, exit 2

Suggested fix

Compute the range as "reachable from HEAD but from no origin ref at all", which is
the set of commits that are genuinely local and therefore safe to amend:

+  local_only_range=(HEAD --not --remotes=origin)
+
   if [[ "$(git config --type=bool commit.gpgsign 2>/dev/null)" == "true" ]]; then
-    unverifiable=$(git log --format='%h %G? %ce' "$upstream..HEAD" 2>/dev/null | awk ...)
+    unverifiable=$(git log --format='%h %G? %ce' "${local_only_range[@]}" 2>/dev/null | awk ...)

-  unpushed=$(git rev-list "$upstream..HEAD" --count 2>/dev/null) || unpushed=0
+  unpushed=$(git rev-list "${local_only_range[@]}" --count 2>/dev/null) || unpushed=0

The invariant this restores: never ask anyone to rewrite a commit that is already
published.
Commits still local are caught exactly as before, so the change is
strictly narrower, not weaker.

Verification

Patched the script in-container and tested four cases:

| Case | Before | After |
|---|---|---|
| Branch repointed at merged master | flags GitHub's merge commit, exit 2 | exit 0 |
| Local commit with wrong committer email, unpushed | caught | caught (unchanged) |
| Same commit after pushing | not caught | not caught (unchanged) |
| Clean tree, branch level with remote | exit 0 | exit 0 |

Notes

  • Observed on 2026-08-02 in a CCR container; hook file timestamp 04:46 (image-provided).
  • The related SessionStart hook session-start-git-identity.sh correctly sets

user.email=noreply@anthropic.com, so the agent's own commits were never the problem.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗