Skip to content
Agent Orchestration
Hook

Hooks

What my-cc-harness runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
my-cc-harness
12543 skills12 agents4 commands10 hooks
Install
> /plugin marketplace add jh941213/my-cc-harness
> /plugin install my-cc-harness@my-cc-harness

Ships with my-cc-harness. Installing the plugin gets these hooks.

What fires, and when

Stop

  • bash ${CLAUDE_PLUGIN_ROOT}/hooks/ralph-loop.sh

TaskCompleted

  • bash ${CLAUDE_PLUGIN_ROOT}/hooks/verify-task-quality.shbash ${CLAUDE_PLUGIN_ROOT}/hooks/docs-sync.sh

TeammateIdle

  • bash ${CLAUDE_PLUGIN_ROOT}/hooks/check-remaining-tasks.sh

SubagentStart

  • Matches*bash ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-tracker.sh

SubagentStop

  • Matches*bash ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-stop-tracker.sh

PostToolUseFailure

  • Matches*bash ${CLAUDE_PLUGIN_ROOT}/hooks/failure-tracker.sh

PostCompact

  • Matches*bash ${CLAUDE_PLUGIN_ROOT}/hooks/post-compact-guard.sh

ConfigChange

  • Matches*bash ${CLAUDE_PLUGIN_ROOT}/hooks/config-change-guard.sh

WorktreeCreate

  • Matches*bash ${CLAUDE_PLUGIN_ROOT}/hooks/worktree-tracker.sh

WorktreeRemove

  • Matches*bash ${CLAUDE_PLUGIN_ROOT}/hooks/worktree-tracker.sh
Read hooks/hooks.json

Where it lives

  • hooks/autodev-judge.shGitHub
  • hooks/check-architecture.shGitHub
  • hooks/check-remaining-tasks.shRunsGitHub
    Read the script
    #!/bin/bash
    # TTH 유휴 리다이렉트: TeammateIdle 훅
    # 미완료 pending 태스크가 있으면 exit 2로 teammate 유휴 방지
    # 모든 태스크 완료 시 exit 0으로 종료 허용
    
    # CLAUDE_TASKS 환경변수에서 pending 태스크 확인
    if [ -n "${CLAUDE_TASKS:-}" ]; then
      PENDING=$(echo "$CLAUDE_TASKS" | grep -c '"status":"pending"' 2>/dev/null || true)
      IN_PROGRESS=$(echo "$CLAUDE_TASKS" | grep -c '"status":"in_progress"' 2>/dev/null || true)
    
      if [ "$PENDING" -gt 0 ] || [ "$IN_PROGRESS" -gt 0 ]; then
        echo "⏳ 아직 미완료 태스크가 ${PENDING}개 (pending), ${IN_PROGRESS}개 (in_progress) 남아있습니다."
        echo "남은 태스크를 클레임하여 작업을 계속하세요."
        exit 2
      fi
    fi
    
    echo "✅ 모든 태스크 완료. 유휴 상태 허용."
    exit 0
    
  • hooks/config-change-guard.shRunsGitHub
    Read the script
    #!/bin/bash
    # ConfigChange: 세션 중 설정 파일 변경 감지
    # 캐시 보존 규칙 위반 시 경고
    set -euo pipefail
    
    CHANGED_FILE="${CLAUDE_CONFIG_PATH:-unknown}"
    TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S")
    
    # 캐시에 영향을 주는 파일 변경 경고
    case "$CHANGED_FILE" in
      *CLAUDE.md*|*rules/*|*agents/*)
        echo "⚠️ [$TIMESTAMP] 캐시 보존 규칙 위반 가능: $CHANGED_FILE 변경됨" >&2
        echo "세션 중 CLAUDE.md/rules/agents 수정은 캐시를 무효화합니다." >&2
        echo "/clear 후 새 세션에서 작업하는 것을 권장합니다." >&2
        ;;
      *settings.json*|*settings.local.json*)
        echo "ℹ️ [$TIMESTAMP] 설정 파일 변경 감지: $CHANGED_FILE" >&2
        echo "일부 설정은 세션 재시작 후 적용됩니다." >&2
        ;;
    esac
    
    exit 0
    
  • hooks/docs-sync.shRunsGitHub
    Read the script
    #!/bin/bash
    # docs-sync.sh — TaskCompleted hook: 스토리 완료 시 docs 동기화 큐에 기록
    # TTH 병렬 작업 중 팀원이 스토리를 완료하면 .docs-queue에 변경사항을 기록한다.
    # ralph-loop.sh가 다음 반복 시 이 큐를 읽어 docs-writer 트리거를 연속 프롬프트(reason)에 포함.
    
    set -euo pipefail
    
    # jq 미설치 환경에서는 조용히 종료
    command -v jq >/dev/null 2>&1 || exit 0
    
    # stdin에서 hook 데이터 읽기
    INPUT=$(cat)
    
    # TTH 모드가 아니면 무시 (.ralph-loop/state.json 존재 확인)
    CWD=$(echo "$INPUT" | /usr/bin/python3 -c "import json,sys; print(json.load(sys.stdin).get('cwd',''))" 2>/dev/null || echo "")
    [ -z "$CWD" ] && exit 0
    
    STATE_FILE="$CWD/.ralph-loop/state.json"
    [ -f "$STATE_FILE" ] || exit 0
    
    # 활성 상태 확인
    ACTIVE=$(/usr/bin/python3 -c "import json; print(json.load(open('$STATE_FILE')).get('active', False))" 2>/dev/null || echo "False")
    [ "$ACTIVE" != "True" ] && exit 0
    
    # git diff로 최근 변경 파일 수집 (마지막 커밋 기준)
    CHANGED_FILES=$(cd "$CWD" && git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
    [ -z "$CHANGED_FILES" ] && exit 0
    
    # .docs-queue 디렉토리 생성
    QUEUE_DIR="$CWD/.docs-queue"
    mkdir -p "$QUEUE_DIR"
    
    # 타임스탬프 기반 큐 파일 생성
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    QUEUE_FILE="$QUEUE_DIR/${TIMESTAMP}.json"
    
    # 태스크 정보 추출
    TASK_ID=$(echo "$INPUT" | /usr/bin/python3 -c "
    import json, sys
    data = json.load(sys.stdin)
    tasks = data.get('tasks', [])
    # 가장 최근 완료된 태스크 찾기
    for t in reversed(tasks):
        if t.get('status') == 'completed':
            print(t.get('id', 'unknown'))
            break
    else:
        print('unknown')
    " 2>/dev/null || echo "unknown")
    
    # 큐에 기록
    /usr/bin/python3 -c "
    import json
    queue_entry = {
        'timestamp': '$TIMESTAMP',
        'task_id': '$TASK_ID',
        'changed_files': '''$CHANGED_FILES'''.strip().split('\n'),
        'status': 'pending'
    }
    with open('$QUEUE_FILE', 'w') as f:
        json.dump(queue_entry, f, indent=2)
    "
    
    exit 0
    
  • hooks/failure-tracker.shRunsGitHub
    Read the script
    #!/bin/bash
    # PostToolUseFailure: 도구 실패 패턴을 progress.txt에 기록
    # 반복 실패 감지 시 에스컬레이션 알림
    set -euo pipefail
    
    PROGRESS="progress.txt"
    FAILURE_LOG=".failure-tracker.jsonl"
    
    # 환경변수에서 실패 정보 추출
    TOOL_NAME="${CLAUDE_TOOL_NAME:-unknown}"
    ERROR="${CLAUDE_TOOL_ERROR:-no error info}"
    TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S")
    
    # 짧은 에러 요약 (첫 줄만)
    ERROR_SUMMARY=$(echo "$ERROR" | head -1 | cut -c1-120)
    
    # 실패 로그 기록 (jsonl)
    if [ -d "$(dirname "$FAILURE_LOG")" ]; then
      echo "{\"ts\":\"$TIMESTAMP\",\"tool\":\"$TOOL_NAME\",\"error\":\"$ERROR_SUMMARY\"}" >> "$FAILURE_LOG" 2>/dev/null || true
    fi
    
    # 동일 도구 최근 3회 연속 실패 감지
    if [ -f "$FAILURE_LOG" ]; then
      RECENT_FAILS=$(tail -10 "$FAILURE_LOG" | grep "\"tool\":\"$TOOL_NAME\"" | wc -l | tr -d ' ')
      if [ "$RECENT_FAILS" -ge 3 ]; then
        echo "⚠️ $TOOL_NAME 3회 이상 연속 실패 — 접근 방식 재검토 필요" >&2
        # progress.txt에 패턴 기록
        if [ -f "$PROGRESS" ]; then
          echo "" >> "$PROGRESS"
          echo "## ⚠️ 반복 실패 감지 [$TIMESTAMP]" >> "$PROGRESS"
          echo "- 도구: $TOOL_NAME" >> "$PROGRESS"
          echo "- 에러: $ERROR_SUMMARY" >> "$PROGRESS"
          echo "- 조치: 다른 접근 방식 시도 필요" >> "$PROGRESS"
        fi
        exit 2
      fi
    fi
    
    exit 0
    
  • hooks/lessons-recall.shGitHub
  • hooks/lessons-stop-gate.shGitHub
  • hooks/lessons-track-edit.shGitHub
  • hooks/memory-postcompact.shGitHub
  • hooks/memory-route-hint.shGitHub
  • hooks/notchi-hook.shGitHub
  • hooks/post-compact-guard.shRunsGitHub
    Read the script
    #!/bin/bash
    # PostCompact: 컨텍스트 압축 후 중요 파일 존재 여부 알림
    # CHECKPOINT.md, progress.txt 등이 있으면 리로드 프롬프트 제공
    set -euo pipefail
    
    TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S")
    REMINDERS=""
    
    # 프로젝트 내구성 파일 체크
    if [ -f "CHECKPOINT.md" ]; then
      REMINDERS="$REMINDERS\n- CHECKPOINT.md 존재 — 현재 마일스톤 상태 확인 필요"
    fi
    
    if [ -f "progress.txt" ]; then
      REMINDERS="$REMINDERS\n- progress.txt 존재 — 팀 학습 메모 참조"
    fi
    
    if [ -f "AUDIT.log" ]; then
      LAST_EVENT=$(tail -1 "AUDIT.log" 2>/dev/null || echo "없음")
      REMINDERS="$REMINDERS\n- AUDIT.log 마지막 이벤트: $LAST_EVENT"
    fi
    
    if [ -d "prd" ]; then
      REMINDERS="$REMINDERS\n- prd/ 디렉토리 존재 — PRD/SPEC 컨텍스트 참조"
    fi
    
    if [ -d ".ralph-loop" ] && [ -f ".ralph-loop/state.json" ]; then
      REMINDERS="$REMINDERS\n- Ralph Loop 활성 — state.json 확인 필요"
    fi
    
    # 알림이 있으면 출력
    if [ -n "$REMINDERS" ]; then
      echo "📋 [PostCompact] 컨텍스트 압축 완료. 다음 파일들을 참조하세요:"
      echo -e "$REMINDERS"
    fi
    
    exit 0
    
  • hooks/ralph-loop.shRunsGitHub
    Read the script
    #!/bin/bash
    # Ralph Loop — Stop Hook Handler
    # Claude Code가 멈출 때 자동으로 다음 반복을 시작하는 Stop Hook
    #
    # 동작 원리:
    #   1. .ralph-loop/state.json이 존재하고 active=true이면 루프 계속
    #   2. 완료 프로미스(<promise>...</promise>)가 트랜스크립트의 assistant 메시지에 있으면 종료
    #      (전체 grep 금지 — 주입 프롬프트/문서 본문의 리터럴 태그 오탐 방지)
    #   3. max_iterations 초과 시 종료
    #   4. 그 외: 연속 프롬프트 전문을 reason에 실어 계속 (Stop 훅 스키마상 reason만 전달됨)
    #
    # 입력: stdin으로 JSON (session_id, transcript_path, cwd 등)
    # 출력: stdout으로 JSON (decision, reason)
    
    set -uo pipefail
    
    INPUT=$(cat)
    
    HOOK_VARS=$(echo "$INPUT" | python3 -c "
    import sys,json
    d=json.load(sys.stdin)
    print(d.get('session_id',''))
    print(d.get('transcript_path',''))
    print(d.get('cwd',''))
    " 2>/dev/null) || HOOK_VARS=""
    SESSION_ID=$(echo "$HOOK_VARS" | sed -n '1p')
    TRANSCRIPT_PATH=$(echo "$HOOK_VARS" | sed -n '2p')
    CWD=$(echo "$HOOK_VARS" | sed -n '3p')
    CWD="${CWD:-$(pwd)}"
    
    STATE_DIR="${CWD}/.ralph-loop"
    STATE_FILE="${STATE_DIR}/state.json"
    
    # 상태 파일이 없으면 루프 비활성 — 정상 종료
    if [ ! -f "$STATE_FILE" ]; then
      echo '{"decision":"approve"}'
      exit 0
    fi
    
    # 필드별 jq 파싱 — 멀티라인 prompt에도 안전 (sed 줄 단위 파싱 금지)
    ACTIVE=$(jq -r '.active // false' "$STATE_FILE" 2>/dev/null) || ACTIVE="false"
    ITERATION=$(jq -r '.iteration // 0' "$STATE_FILE" 2>/dev/null) || ITERATION=0
    MAX_ITER=$(jq -r '.max_iterations // 100' "$STATE_FILE" 2>/dev/null) || MAX_ITER=100
    PROMPT=$(jq -r '.prompt // ""' "$STATE_FILE" 2>/dev/null) || PROMPT=""
    COMPLETION=$(jq -r '.completion_promise // "DONE"' "$STATE_FILE" 2>/dev/null) || COMPLETION="DONE"
    
    if [ "$ACTIVE" != "true" ]; then
      echo '{"decision":"approve"}'
      exit 0
    fi
    
    # 완료 프로미스 감지 — 트랜스크립트 JSONL에서 assistant 메시지 텍스트만 검사
    PROMISE_FOUND=false
    if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
      if RALPH_T="$TRANSCRIPT_PATH" RALPH_TAG="<promise>${COMPLETION}</promise>" python3 -c "
    import json, os, sys
    tag = os.environ['RALPH_TAG']
    found = False
    with open(os.environ['RALPH_T']) as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                d = json.loads(line)
            except json.JSONDecodeError:
                continue
            m = d.get('message') if isinstance(d.get('message'), dict) else d
            role = d.get('type') or m.get('role')
            if role != 'assistant':
                continue
            c = m.get('content', '')
            if isinstance(c, list):
                c = ''.join(x.get('text', '') for x in c if isinstance(x, dict))
            if tag in str(c):
                found = True
                break
    sys.exit(0 if found else 1)
    " 2>/dev/null; then
        PROMISE_FOUND=true
      fi
    fi
    
    # 종료 조건 체크
    if [ "$PROMISE_FOUND" = "true" ]; then
      RALPH_STATE_FILE="$STATE_FILE" python3 -c "
    import json, os, datetime
    f = os.environ['RALPH_STATE_FILE']
    s = json.load(open(f))
    s['active'] = False
    s['completed_at'] = datetime.datetime.now().isoformat()
    s['status'] = 'completed'
    json.dump(s, open(f, 'w'), indent=2, ensure_ascii=False)
    " 2>/dev/null
      echo '{"decision":"approve"}'
      exit 0
    fi
    
    if [ "$ITERATION" -ge "$MAX_ITER" ]; then
      RALPH_STATE_FILE="$STATE_FILE" python3 -c "
    import json, os, datetime
    f = os.environ['RALPH_STATE_FILE']
    s = json.load(open(f))
    s['active'] = False
    s['completed_at'] = datetime.datetime.now().isoformat()
    s['status'] = 'max_iterations_reached'
    json.dump(s, open(f, 'w'), indent=2, ensure_ascii=False)
    " 2>/dev/null
      echo '{"decision":"approve"}'
      exit 0
    fi
    
    # 반복 증가
    NEW_ITER=$((ITERATION + 1))
    RALPH_STATE_FILE="$STATE_FILE" RALPH_NEW_ITER="$NEW_ITER" python3 -c "
    import json, os, datetime
    f = os.environ['RALPH_STATE_FILE']
    s = json.load(open(f))
    s['iteration'] = int(os.environ['RALPH_NEW_ITER'])
    s['last_iteration_at'] = datetime.datetime.now().isoformat()
    json.dump(s, open(f, 'w'), indent=2, ensure_ascii=False)
    " 2>/dev/null
    
    # AUDIT.log에 기록
    AUDIT_FILE="${CWD}/AUDIT.log"
    if [ -f "$AUDIT_FILE" ]; then
      echo "[$(date -u +%Y-%m-%dT%H:%M:%S)] ralph-loop ITERATION ${NEW_ITER}/${MAX_ITER}" >> "$AUDIT_FILE"
    fi
    
    # docs 큐 확인 — 대기 중인 docs 동기화가 있으면 프롬프트에 포함
    DOCS_QUEUE_DIR="${CWD}/.docs-queue"
    DOCS_INSTRUCTION=""
    if [ -d "$DOCS_QUEUE_DIR" ]; then
      PENDING_COUNT=$(find "$DOCS_QUEUE_DIR" -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
      if [ "$PENDING_COUNT" -gt "0" ]; then
        CHANGED=$(RALPH_DOCS_DIR="$DOCS_QUEUE_DIR" python3 -c "
    import json, glob, os
    d = os.environ['RALPH_DOCS_DIR']
    files = set()
    for f in glob.glob(os.path.join(d, '*.json')):
        try:
            data = json.load(open(f))
            files.update(data.get('changed_files', []))
        except (json.JSONDecodeError, OSError): pass
    print(', '.join(sorted(files)[:20]))
    " 2>/dev/null || echo "")
    
        if [ -n "$CHANGED" ]; then
          DOCS_INSTRUCTION="
    📝 docs 동기화 필요: ${CHANGED} — docs-writer background 스폰."
        fi
    
        if [ -f "$AUDIT_FILE" ]; then
          echo "[$(date -u +%Y-%m-%dT%H:%M:%S)] docs-writer TRIGGER pending=${PENDING_COUNT}" >> "$AUDIT_FILE"
        fi
      fi
    fi
    
    # 기존 팀원 정보 수집 (tmux 세션 재사용)
    TEAMMATES_FILE="${STATE_DIR}/teammates.json"
    TEAMMATE_INSTRUCTION=""
    if [ -f "$TEAMMATES_FILE" ]; then
      TEAMMATE_INFO=$(RALPH_TEAMMATES="$TEAMMATES_FILE" python3 -c "
    import json, os
    try:
        f = os.environ['RALPH_TEAMMATES']
        data = json.load(open(f))
        running = {k: v for k,v in data.get('teammates',{}).items() if v.get('status')=='running'}
        if running:
            labels = [v.get('name', k) for k,v in running.items()]
            names = ', '.join(labels)
            print(f'\n⚡ 활성 팀원: {names} — SendMessage(to=이름)로 재사용. 새 Agent() 금지.')
        else:
            print('')
    except (json.JSONDecodeError, OSError, KeyError):
        print('')
    " 2>/dev/null || echo "")
      TEAMMATE_INSTRUCTION="$TEAMMATE_INFO"
    fi
    
    # 연속 프롬프트 — Stop 훅 스키마상 Claude에게 전달되는 것은 reason뿐이므로 전문을 reason에 싣는다
    CONTINUATION="[Ralph Loop ${NEW_ITER}/${MAX_ITER}] ${PROMPT}${TEAMMATE_INSTRUCTION}${DOCS_INSTRUCTION}
    완료 시 <promise>${COMPLETION}</promise> 출력."
    
    # JSON escape — temp file로 안전하게 처리
    PROMPT_TMP="${STATE_DIR}/.inject_prompt.tmp"
    printf '%s' "$CONTINUATION" > "$PROMPT_TMP"
    ESCAPED_PROMPT=$(RALPH_TMP="$PROMPT_TMP" python3 -c "
    import json, os
    with open(os.en
  • hooks/reset-home-memory.shGitHub
  • hooks/subagent-stop-tracker.shRunsGitHub
  • hooks/subagent-tracker.shRunsGitHub
  • hooks/verify-task-quality.shRunsGitHub
  • hooks/work-protocol-prompt.shGitHub
  • hooks/worktree-tracker.shRunsGitHub

All 20 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.

Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.

Ships withmy-cc-harness

my-claude-code-asset

Get the whole plugin