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から組み立てるのが正解。
code:.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本体
code: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 して使う汎用の委譲役
code:_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
---
nana.icon AIによる っていってもかなり手で直してるんだけど……。
commit msgに刻むけど、他の人からは参照できない値なのでgit noteのほうが良いのではという説もある。
#AIによる記