Claude Code likes to gh pr edit PRs without reading the current state first — overwriting descriptions, losing context, or making changes based on stale assumptions. This hook pair forces a "read before write" pattern: any gh pr edit is blocked unless gh pr view was called first for that PR in the same session.
Two hooks — a PostToolUse hook records when Claude views a PR, and a PreToolUse hook blocks edits unless a view was recorded. The view is consumed after each edit, so consecutive edits each require a fresh read.
gh pr view 123 ... → records "123" to a temp file
gh pr edit 123 ... → checks file, allows, then removes "123"
gh pr edit 123 ... → blocked — must view again first
State is per-session (temp file keyed by $SESSION_ID), so it resets naturally.
~/.claude/hooks/check-pr-viewed.sh (PreToolUse — blocks edits)
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')
# Only care about gh pr edit commands
if echo "$COMMAND" | grep -qE '\bgh\s+pr\s+edit\b'; then
PR_NUM=$(echo "$COMMAND" | grep -oE '\bedit\s+([0-9]+)' | grep -oE '[0-9]+')
STATE_FILE="/tmp/claude-pr-viewed-${SESSION_ID}.txt"
if [ -n "$PR_NUM" ]; then
if ! grep -qx "$PR_NUM" "$STATE_FILE" 2>/dev/null; then
echo "Blocked: run 'gh pr view $PR_NUM --json body -q .body' first to read the current PR state before editing." >&2
exit 2
fi
# Consume the view — next edit requires a fresh view
sed -i '' "/^${PR_NUM}$/d" "$STATE_FILE"
fi
fi
exit 0~/.claude/hooks/record-pr-view.sh (PostToolUse — records views)
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')
# Only care about gh pr view commands
if echo "$COMMAND" | grep -qE '\bgh\s+pr\s+view\b'; then
PR_NUM=$(echo "$COMMAND" | grep -oE '\bview\s+([0-9]+)' | grep -oE '[0-9]+')
if [ -n "$PR_NUM" ]; then
echo "$PR_NUM" >> "/tmp/claude-pr-viewed-${SESSION_ID}.txt"
fi
fi
exit 0Make both executable:
chmod +x ~/.claude/hooks/check-pr-viewed.sh ~/.claude/hooks/record-pr-view.shIn ~/.claude/settings.json, add to the hooks object:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/check-pr-viewed.sh",
"timeout": 5,
"statusMessage": "Checking PR view status..."
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/record-pr-view.sh",
"timeout": 5,
"statusMessage": "Tracking PR views..."
}
]
}
]
}
}The sed -i '' syntax is macOS. On Linux, use sed -i (no empty string argument).
PreToolUse:Bash hook error: [~/.claude/hooks/check-pr-viewed.sh]:
Blocked: run 'gh pr view 123 --json body -q .body' first to read the current PR state before editing.
Claude then reads the PR and retries — exactly the behavior you want.