Stop hook (stop-hook-git-check.sh) tells the agent to rewrite published history after a PR merges
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 bynoreply@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:
--amendon a commit already onorigin/masterrewrites 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 isorigin/<current-branch>.
The moment a PR merges, origin/<branch> is frozen at the pre-merge tip whilemaster 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.shcorrectly sets
user.email=noreply@anthropic.com, so the agent's own commits were never the problem.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗