agentsclimarketplace

Hook log append pattern

Skill bokuwalily/claude-code-skills/skills/hook-log-append-pattern

75 battle-tested Claude Code skills from shipping 20+ apps solo — macOS, Next.js, Vercel, iOS/Expo, Cloudflare, Phaser, local AI media & more

Install
npx -y skills add bokuwalily/claude-code-skills --skill hook-log-append-pattern

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Claude Code の Stop/PostToolUse フックから構造化データを JSONL ログに追記する汎用パターン。stop_cost_log.sh / post_audit_log.sh / stop_agent_tracker.sh のような「stdin → parse → append」ループを新規で書くときに参照する。

SKILL.md

4.7 KB, as published. Nobody here has run it

Procedure

1. hook stdin の構造

Stop フック・PostToolUse フック両方とも Claude Code から 標準入力 で JSON ペイロードを受け取る。

INPUT=$(cat)
[ -z "$INPUT" ] && exit 0   # 空入力は即 exit 0(エラーにしない)
フック種別主要フィールド
Stopsession_id, transcript_path, cwd, hook_event_name
PostToolUsetool_name, tool_input, tool_response, session_id

2. 追記先ファイルの準備

LOG_DIR="$HOME/.claude/logs"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/my-feature.jsonl"
  • .jsonl 形式(1行1JSON)にすると jq でのフィルタリングが容易
  • 月別ファイル分割は "$LOG_DIR/$(date +%Y-%m).jsonl" で簡単に実現

3. bash + inline python3 パターン(推奨)

外部依存ゼロ。python3 は macOS/Linux 標準搭載。

export HOOK_INPUT="$INPUT"
export LOG_PATH="$LOG_FILE"

python3 - <<'PY'
import os, sys, json, datetime

try:
    data = json.loads(os.environ.get("HOOK_INPUT", ""))
except Exception:
    sys.exit(0)

# --- 必要フィールドを抽出 ---
sid = data.get("session_id", "")
cwd = data.get("cwd", "")

record = {
    "ts":         datetime.datetime.now().isoformat(timespec="seconds"),
    "session_id": sid,
    "cwd":        cwd,
    # 追加したいフィールドをここに
}

with open(os.environ["LOG_PATH"], "a", encoding="utf-8") as f:
    f.write(json.dumps(record, ensure_ascii=False) + "\n")
PY

exit 0   # フックは必ず 0 で終わる(非0 は Claude Code への拒否シグナル)

4. transcript_path を使った処理

Stop フックでは transcript_path が渡る。存在チェックを必ずする:

tp = data.get("transcript_path", "")
if not tp or not os.path.exists(tp):
    sys.exit(0)

with open(tp, "r", encoding="utf-8", errors="replace") as f:
    for line in f:
        try:
            rec = json.loads(line)
        except Exception:
            continue
        # rec を処理して record に集計

5. 重複防止(Stop フックで同セッションを二重記録しない)

seen_ids = set()
if os.path.exists(LOG_PATH):
    with open(LOG_PATH) as f:
        for line in f:
            try:
                r = json.loads(line)
                if r.get("session_id") == sid:
                    seen_ids.add(r.get("unique_key"))
            except Exception:
                continue
# 書き込み前に seen_ids チェック

6. フックの接続(settings.json)

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "$HOME/.claude/hooks/my_log_hook.sh"
          }
        ]
      }
    ]
  }
}

PostToolUse はツール名で絞り込める:

"PostToolUse": [
  {
    "matcher": "Write|Edit",
    "hooks": [{"type": "command", "command": "$HOME/.claude/hooks/audit_log.sh"}]
  }
]

Pitfalls

  • exit 0 を忘れると Claude Code がブロックされる: フックが非 0 で終了すると操作が拒否される。try/except で必ず sys.exit(0) を呼ぶ
  • フルスキャン問題: Stop フックは毎回 transcript を全行読む。セッションが長い(1000行+)と遅くなる。重い処理は & でバックグラウンド化し exit 0 だけ先に返す
  • stat -f %m は macOS 専用: タイムスタンプ取得は Linux で stat -c %Y。クロスプラットフォームなら python3 -c "import os; print(os.path.getmtime(...))" が安全
  • env 経由の文字列長: export HOOK_INPUT="$INPUT" で渡せるサイズは OS 制限(通常 256KB)。大きなペイロードは tmpfile 経由にする
  • LOG_DIR が存在しない: mkdir -p を常に先頭で実行する。cron/launchd 起動時はホームが確定しないことがある

Verification

# 直近エントリを確認
tail -3 ~/.claude/logs/my-feature.jsonl | jq .

# ログの整合性チェック
jq -c . ~/.claude/logs/my-feature.jsonl | wc -l  # 壊れた行があれば jq がエラーを出す

# hook を手動トリガー(Stop hook は stdin が必要)
echo '{"session_id":"test","transcript_path":"","cwd":"/tmp","hook_event_name":"Stop"}' \
  | bash ~/.claude/hooks/my_log_hook.sh
echo "exit=$?"

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.