AIエージェントのセッションIDをcommit messageに残すgit hookを作ってもらった
Claude CodeみたいなAIエージェントと開発していると、後から「これ何で実装したんだっけ」「当時どう指示したんだっけ」を掘り起こしたくなる事がある。
そこでcommit messageのfooterにセッションIDをtrailerとして自動で埋めるgit hookを作ってもらった。
やること
commit messageにこういうtrailerを1行足す。
Agent-Session: Claude Code on <hostname>: <session-id> prepare-commit-msgフックで、環境変数
CLAUDE_CODE_SESSION_ID がある時だけ付与する。 人間が素の端末で打つcommitには付かない。
こうすると、
Agent-Session: Claude Code on 29-er: 73b4bffb-0cd2-4f22-8d0d-55a6fc087252 みたいなのができる。 これは実際にこれをつくってもらった時のsession id。
同じマシンなら
claude --resume <id> で会話を再開できる。 セッションログはローカルにしか無いので、resumeは基本そのホスト限定。
ハマりどころ: core.hooksPathをグローバルに張ると危ない。
git config --global core.hooksPath ... で全repoに効かせると、gitは各repoの .git/hooks/ を一切見なくなる。 repo独自のpre-commit(lintやgofmt等)が黙って無効化される。
なので、グローバルフックから各repoの
.git/hooks/<同名> を呼び直す委譲スクリプトを噛ませた。 委譲先の解決は
git rev-parse --git-path hooks/X だと core.hooksPath 設定下でグローバル側を指してしまう。 --absolute-git-dir から組み立てるのが正解。.gitconfig[core] hooksPath = /Users/nana/.claude/git-hooksこんな風に……。
_chain-to-local は、 /Users/nana/.claude/git-hooks 以下に applypatch-msg , commit-msg , post-checkout , post-commit , post-merge , pre-applypatch , pre-commit , pre-merge-commit , pre-push , pre-rebase についてそれを指すsymlinkをつくってもらった。hook本体
prepare-commit-msg#!/bin/sh# prepare-commit-msg: AIエージェントのセッションIDを commit message の trailer に付与する。COMMIT_MSG_FILE="$1"COMMIT_SOURCE="$2"
host=$(hostname -s)
add_trailer() { agent="$1" id="$2" # 既に同じIDが書かれていれば二重付与しない(amend / rebase 対策) if grep -qF "$id" "$COMMIT_MSG_FILE" 2>/dev/null; then return fi git interpret-trailers --in-place \ --trailer "Agent-Session=${agent} on ${host}: ${id}" "$COMMIT_MSG_FILE"}
# merge / squash のメッセージには付けない(特定セッションの成果物ではないため)case "$COMMIT_SOURCE" in merge|squash) ;; *) [ -n "$CLAUDE_CODE_SESSION_ID" ] && add_trailer "Claude Code" "$CLAUDE_CODE_SESSION_ID" ;;esac
# core.hooksPath で上書きされて動かなくなる repo 固有の prepare-commit-msg を補って呼ぶ。# core.hooksPath 設定下では `--git-path hooks/X` がグローバル側を指すため、# core.hooksPath の影響を受けない --absolute-git-dir から repo本来のパスを組む。git_dir=$(git rev-parse --absolute-git-dir 2>/dev/null)repo_hook="$git_dir/hooks/prepare-commit-msg"if [ -n "$git_dir" ] && [ -x "$repo_hook" ] && [ "$repo_hook" != "$0" ]; then exec "$repo_hook" "$@"fi
exit 0 case "$COMMIT_SOURCE" のところになんかうまいこと他のAIのsession idの分岐も仕込めば他のAIでも行けるはず。各フック名から symlink して使う汎用の委譲役
_chain-to-local#!/bin/sh# 汎用フック委譲役。## グローバル core.hooksPath を張ると git は repo の .git/hooks/ を一切見なくなる。# このスクリプトを各フック名にシンボリックリンクしておき、呼ばれたフック名と同名の# repo固有フック (.git/hooks/<name>) があればそれを exec する。# これで「グローバル設定」と「repo固有フック」を共存させ、後者が黙って無効化される# 事故を防ぐ。
hook=$(basename "$0")# 注意: core.hooksPath が設定されていると `git rev-parse --git-path hooks/X` は# その設定先(=このグローバル側)を返してしまう。repo本来の .git/hooks/ を得るには# core.hooksPath の影響を受けない --absolute-git-dir から組み立てる。git_dir=$(git rev-parse --absolute-git-dir 2>/dev/null)repo_hook="$git_dir/hooks/$hook"
if [ -n "$git_dir" ] && [ -x "$repo_hook" ] && [ "$repo_hook" != "$0" ]; then exec "$repo_hook" "$@"fi
exit 0---
commit msgに刻むけど、他の人からは参照できない値なのでgit noteのほうが良いのではという説もある。