Problem
The block-unreviewed-merge.sh hook checks that .claude/session/reviews/<pr>-ceo.approved exists and its SHA matches HEAD. But Claude can write this marker directly via echo SHA > file without the user ever approving — bypassing the entire point of the merge gate.
Incident
PR me2resh/curios-dog#201 was merged with a CEO marker that Claude wrote without explicit user approval.
Proposed fix
The /approve-merge skill should write a signed marker instead of a plain SHA:
<sha>
approved_by=user
approved_at=<ISO-8601>
message_hash=<hash of the user's approval message>
The hook then verifies:
- SHA matches HEAD (existing)
approved_by=user is present (new — raw echo won't include this)
- Optionally:
message_hash matches a recent user message (strongest guarantee)
This makes raw echo SHA > file insufficient to pass the gate — the marker must have the structured fields that only the skill writes after verifying user intent.
Problem
The
block-unreviewed-merge.shhook checks that.claude/session/reviews/<pr>-ceo.approvedexists and its SHA matches HEAD. But Claude can write this marker directly viaecho SHA > filewithout the user ever approving — bypassing the entire point of the merge gate.Incident
PR me2resh/curios-dog#201 was merged with a CEO marker that Claude wrote without explicit user approval.
Proposed fix
The
/approve-mergeskill should write a signed marker instead of a plain SHA:The hook then verifies:
approved_by=useris present (new — rawechowon't include this)message_hashmatches a recent user message (strongest guarantee)This makes raw
echo SHA > fileinsufficient to pass the gate — the marker must have the structured fields that only the skill writes after verifying user intent.