Free

ניהול תהליך Git עם Hooks: גם Claude צריך לציית לכללים

השתמשו ב-PreToolUse Hooks ליירוט פקודות git ב-Bash — חסמו commits ישירים ל-main, פורמטים שגויים ופעולות מסוכנות.


ל-Claude Code יש הרשאה להריץ ישירות git commit, git push ו-git checkout. זה נוח ברוב המקרים — אבל גם אומר ש-Claude עלול:

  • לבצע commit ישירות ל-main
  • לכתוב הודעות commit בפורמט произволний
  • לשכוח להוסיף שיוך לכותב-שותף
  • לבצע force push כשלא צריך

Hooks מאפשרים ליירט פעולות אלו לפני שהן מתבצעות — לאמת, לאכוף סטנדרטים, או לחסום פעולות מסוכנות לגמרי.


הגישה: PreToolUse ליירוט פקודות git ב-Bash

כל פעולות git ב-Claude Code מתבצעות דרך כלי ה-Bash. לכן נקודת היירוט היא:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [...]
      }
    ]
  }
}

ה-Hook מקבל את מחרוזת הפקודה המלאה דרך stdin. בודקים אם היא מכילה פעולות git ספציפיות, ואז מחליטים — לאפשר או לחסום.

תבנית בסיסית:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

if echo "$cmd" | grep -qE '^git commit'; then
  echo "לא עומד בסטנדרטים" >&2
  exit 2  # חוסמים ומודיעים ל-Claude
fi

כלל 1: איסור commits ישירים ל-main

הדרישה הנפוצה ביותר. Claude צריך לעבוד על ענפי feature, לא לדחוף ישירות ל-main.

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -qE '^git (commit|push)' || exit 0

current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)

if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
  echo "commits ישירים ל-$current_branch אסורים. עבור תחילה לענף feature." >&2
  exit 2
fi

כשקלוד יקבל הודעה זו, הוא יעבור בעצמו לענף מתאים לפני שימשיך.


כלל 2: פורמט חובה להודעת commit

קבוצות רבות משתמשות ב-Conventional Commits: feat: ..., fix: ..., docs: .... אך הודעות של Claude לא בהכרח יתאימו לפורמט שלכם.

מאמתים לפני ה-commit:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -qE '^git commit' || exit 0

# מוציאים את ההודעה אחרי -m
msg=$(echo "$cmd" | grep -oP '(?<=-m )["\x27].*?["\x27]' | tr -d '"'"'" || true)

if [[ -z "$msg" ]]; then
  exit 0  # אין דגל -m, כנראה משתמשים בעורך — דולגים
fi

if ! echo "$msg" | grep -qE '^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{3,}'; then
  echo "פורמט הודעת ה-commit שגוי." >&2
  echo "פורמט נדרש: <type>(<scope>): <description>" >&2
  echo "דוגמה: feat(auth): add OAuth login" >&2
  echo "סוגים מותרים: feat | fix | docs | style | refactor | test | chore | perf | ci | build | revert" >&2
  exit 2
fi

Claude יקבל משוב ויכתוב מחדש את הודעת ה-commit בפורמט הנכון.


כלל 3: שיוך כותב-שותף חובה

אתם רוצים שכל commit של Claude יכלול שיוך — למעקב אחר מעורבות ה-AI. אבל לא רוצים להקליד זאת ידנית בכל פעם.

PostToolUse מתאים יותר כאן — בודקים אחרי שה-commit הסתיים ומבקשים מ-Claude לבצע --amend אם חסר השיוך:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -qE '^git commit' || exit 0
echo "$cmd" | grep -q 'amend' && exit 0  # מונעים לולאה אינסופית

last_msg=$(git log -1 --format="%B" 2>/dev/null)
if ! echo "$last_msg" | grep -q 'Co-Authored-By'; then
  echo "ב-commit חסר שיוך Co-Authored-By." >&2
  echo "הרץ: git commit --amend -m \"\$(git log -1 --format='%s')\" --trailer 'Co-Authored-By: Claude Sonnet 4.6 <[email protected]>'" >&2
  exit 2
fi

כלל 4: חסימת פעולות git מסוכנות

חלק מהפקודות קשות לביטול:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

# חוסמים force push לענפים ראשיים
if echo "$cmd" | grep -qE 'git push.*--force(-with-lease)?.*\b(main|master)\b'; then
  echo "force push ל-main/master אסור." >&2
  exit 2
fi

# חוסמים reset --hard (מחייבים אישור מפורש)
if echo "$cmd" | grep -qE 'git reset --hard'; then
  echo "git reset --hard היא פעולה הרסנית — נחסמה. אם באמת צריך, הוסף הערה # ALLOW: עם פירוט הסיבה." >&2
  exit 2
fi

# חוסמים מחיקת ענפים מרוחקים
if echo "$cmd" | grep -qE 'git push.*--delete|git push.*:'; then
  echo "מחיקת ענפים מרוחקים אסורה. עשה זאת ידנית ב-GitHub אם צריך." >&2
  exit 2
fi

הטריק עם # ALLOW: עבור reset --hard מאפשר ל-Claude "לבטל את הנעילה" כשהפעולה באמת נחוצה — תוך דרישה לנימוק מפורש.


כלל 5: יומן ביקורת git

מתעדים את כל פעולות git שביצע Claude:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -q '^git' || exit 0

echo "$(date '+%Y-%m-%d %H:%M:%S') GIT: $cmd" >> ~/.claude/git-audit.log

השתמשו ב-PostToolUse — מתעדים רק פקודות שבוצעו בפועל.


קונפיגורציה מלאה: Hook משולב לתהליך git

.claude/hooks/git-guard.sh:

#!/bin/bash
set -e

input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // empty')

[[ -z "$cmd" ]] && exit 0
echo "$cmd" | grep -q '^git' || exit 0

current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")

# 1. חוסמים commits ו-pushes ל-main/master
if echo "$cmd" | grep -qE '^git (commit|push)'; then
  if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
    echo "הענף הנוכחי הוא $current_branch — commits ו-pushes ישירים חסומים. עבור לענף feature." >&2
    exit 2
  fi
fi

# 2. אימות פורמט הודעת commit
if echo "$cmd" | grep -qE '^git commit.*-m'; then
  msg=$(echo "$cmd" | grep -oP '(?<=-m )["\x27][^\x27"]*["\x27]' | tr -d '"'"'" || true)
  if [[ -n "$msg" ]] && ! echo "$msg" | grep -qE '^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{3,}'; then
    echo "פורמט הודעת ה-commit שגוי." >&2
    echo "פורמט: <type>(<scope>): <description>" >&2
    echo "דוגמה: fix(api): handle null response from upstream" >&2
    exit 2
  fi
fi

# 3. חוסמים פעולות מסוכנות (אלא אם יש הערת ALLOW)
if echo "$cmd" | grep -qE 'git push.*--force|git reset --hard|git push.*--delete'; then
  if ! echo "$cmd" | grep -q '# ALLOW:'; then
    echo "פעולה מסוכנת נחסמה: $cmd" >&2
    echo "כדי להמשיך, הוסף בסוף הפקודה # ALLOW: <סיבה>" >&2
    exit 2
  fi
fi

settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/git-guard.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); echo \"$cmd\" | grep -q \"^git\" && echo \"$(date +\\'%Y-%m-%d %H:%M:%S\\') $cmd\" >> ~/.claude/git-audit.log || true'"
          }
        ]
      }
    ]
  }
}

כמה דברים שכדאי לדעת

השתמשו ב-Bash כ-matcher, לא ב-Git

ל-Claude Code אין כלי Git ייעודי — כל פקודות git עוברות דרך Bash. ה-matcher חייב להיות "Bash".

אל תסמכו יתר על המידה על ביטויים רגולריים

פקודות git מגיעות בצורות רבות: git commit -m "msg", git commit --message="msg", GIT_AUTHOR_NAME=xxx git commit. regex נוקשה מדי יפספס מקרי קצה. יירטו את התבניות המסוכנות הנפוצות ביותר ובטחו בשיקול הדעת של Claude לגבי השאר.

קונפיגורציה גלובלית מול פרוייקטלית

הגנה על הענף הראשי ואימות פורמט commits — בקונפיגורציה פרוייקטלית (.claude/settings.json), כי שמות ענפים ומוסכמות commits משתנים בין פרוייקטים. יומן ביקורת — בקונפיגורציה גלובלית, כתיבה לקובץ משותף ~/.claude/git-audit.log.


סיכום

ניהול תהליך git עם Hooks מסתכם ב: PreToolUse + Bash matcher + זיהוי פקודות git.

שלושה כללים שכדאי להגדיר ראשונים:
1. איסור commits ישירים ל-main
2. פורמט חובה להודעות commits
3. חסימת force push, reset --hard ופעולות הרסניות אחרות

בשילוב עם יומן ביקורת, כל פעולת git של Claude הופכת לניתנת למעקב. התחילו מהכלל הפשוט ביותר, ודאו שהוא עובד, ואז הוסיפו את הבא.