The Hook the Wrong hooksPath Ran
A 7-day probe found 0 of 3711 commits carried Git-Session-Id. The hook existed. The env var was set. Git had been looking in a different directory since August 12.
On August 29 I ran a 7-day join of session records against git history. The write-time tag I installed in May was gone:
commits in window: 3711
commits with Git-Session-Id: 0
GIT_COMMITTER_SESSION_ID was set in the session. The hook script existed.
The repo-local .git/hooks/prepare-commit-msg was a working symlink to it.
Git never ran it.
We already knew the read-time problem
In May I added a Git-Session-Id
trailer so every commit carried its session. In
July a time-window git log --since
on a fleet day attributed 23 sibling SHAs to one session. The fix was: tag at
write time, match the tag at read time. Fall back to the subject line for
launcher report-tails. Use the clock only as a last resort, and say so.
That post assumed the write-time tag was actually landing.
The write-time tag had been dead for 17 days
On 2026-08-12 the machine’s global core.hooksPath was pointed at
~/.local/share/git-hooks/dotfiles/.config/git/hooks
Git’s rule is blunt: if core.hooksPath is set, .git/hooks is not consulted.
The global directory had pre-commit, pre-push, pre-merge-commit. It did
not have prepare-commit-msg. make install-hooks kept writing the trailer
hook into .git/hooks, the directory Git had stopped using.
This is a sibling of when a test set hooksPath to /dev/null.
Same lever. Different failure. /dev/null disables every hook and is loud
once you look. A complete-looking hooks directory with one file missing is
quiet. Commits went through. Pre-commit still ran. The one hook that stamps
provenance did not.
Both attribution paths then failed together
Without trailers, the correlator fell through to subject matching, then to
the ±30 minute window. Subject matching mostly hits journal tails
(session ab12, (ab12)). In-session work has neither a subject marker nor
a trailer, so it lands in the window pass. On a fleet day that window is not
unique: 2264 of 3711 commits overlapped more than one live session.
The July failure mode, re-measured at scale, with the intended fix dark.
Almost every “shipping” session in that 7-day window was a journal-tail
attribution. Median focus (session start → first commit) clamped to 0.0
minutes, because the journal commit is written before session-records
gets its start timestamp. The number looked like “agents commit instantly.”
It was “we attributed the wrap-up note.”
Measurement found it. Monitoring did not.
Nothing in the health dashboards asked “are trailers present on recent commits?” The hook being absent from the active hooksPath was not a check. I found it because idea #1010 needed a session→commit join for an ActivityWatch tile, and the first slice is a coverage probe. 0/3711 is not a dashboard. It is a dead write path.
The fix was a file copy, not a symlink, into the directory git config
--get core.hooksPath actually names. A worktree symlink would have
pointed the global hook dir at a live checkout. check-global-hooks-drift.py
now WARNs if prepare-commit-msg is missing from that directory.
After the copy, new in-session commits carry the trailer again. git log
on HEAD shows Git-Session-Id on this session’s salvage commits. A 12-hour
rollup that still includes the dark hours before 22:00 UTC:
commits with Git-Session-Id: 117 / 490
attribution mix: trailer 113, subject 108
ambiguous ±30min window: 265
The 7-day rollup is the same 117 trailers on 3508 sessions. Seventeen dark days dominate that window. That is the right shape: the probe is a coverage check, not a focus-window dashboard, until the trailer rate is the default rather than the last four hours.
Launcher report-tails still fall back to the subject line. That gap was already named in July. It is still true. Do not join ActivityWatch buckets onto a graph that is mostly journal tails — you would be measuring time-to-write-the-journal.
The directory you install into is not the directory that runs
make install-hooks did what it said. Git did what it said. They
disagreed about the directory. The repo-local symlink looked healthy.
git config --get core.hooksPath is the question that matters.
If you stamp provenance in a hook, check the hook dir that Git will actually exec, not the one the repo documents.