Skip to content

feat: git post-commit hook + cli install-git-hook#37

Open
jakduch wants to merge 4 commits into
josepe98:mainfrom
jakduch:feat/git-hook-integration
Open

feat: git post-commit hook + cli install-git-hook#37
jakduch wants to merge 4 commits into
josepe98:mainfrom
jakduch:feat/git-hook-integration

Conversation

@jakduch
Copy link
Copy Markdown
Collaborator

@jakduch jakduch commented May 23, 2026

What does this add and why do you believe it belongs in this dashboard?

This adds a tiny, dependency-free post-commit git hook plus a cli.py install-git-hook command that wires it into the user's repos. The hook writes one JSON record per commit to ~/.claude/git-trace.jsonl (repo, sha, message, author, timestamp, session_id), and get_dashboard_data surfaces the last 50 entries to the frontend, where a new insight card lists the top sessions that produced commits in the last 7 days, linkified to the existing session-detail panel.

The whole point of a personal Claude Code usage dashboard is the question "what did I actually get out of this token spend?" — and the most concrete answer is the commits that landed. Today the dashboard can show that session abc123 cost $4.20 and ran 38 turns, but it can't say that's the session that shipped the auth refactor. This PR closes that loop: every commit tags itself with the session that produced it (via $CLAUDE_SESSION_ID, with a 10-minute most-recently-touched-JSONL fallback for plain CLI sessions), so the dashboard finally connects spend to outcome. It's opt-in via install-git-hook, scoped per-repo by default (or --global), and the hook always exit 0s so it can never block a commit.

Checklist

Code correctness

  • All calcCost() calls pass 6 arguments: (model, inp, out, cache_read, cache_creation, cache_1h) — N/A, no new calcCost call sites
  • JavaScript template literals use bare backticks (`), not escaped ones (\`)
  • No JS variables referenced before they are defined — renderGitTraceCard / selectSessionByFullId / esc are all hoisted function declarations
  • No new third-party dependencies introduced — bash + POSIX tools + stdlib shutil/subprocess

Tests

  • python3 -m unittest discover -s tests -v — all 204 passing
  • python3 -m unittest tests.test_browser -v — all 6 passing
  • New behaviour is covered by at least one test — tests/test_git_hook.py (13 tests: hook script presence, end-to-end commit + JSONL append, JSON-escaping of tricky messages, install-git-hook per-repo install, outside-repo exit code, _load_git_trace parsing + corruption tolerance + /api/data payload integration)

Scope

  • This is a single concern — one feature or fix per PR
  • Only touches existing files (dashboard.py, scanner.py, cli.py, pricing.py, cowork.py, tests/) — or I've explained below why a new file is needed

New files needed for this feature:

  • hooks/post-commit — the shipped bash hook. Has to live as a standalone file so install-git-hook can shutil.copy2 it into <repo>/.claude-usage-hooks/ or ~/.git-hooks/, and so chmod +x is meaningful in source control.
  • tests/test_git_hook.py — co-located with the rest of the test suite.

Audit fixes applied on top of the original branch

  • hooks/post-commit: refactored the SESSION_ID-resolution block. The original nested "$(find ... | while ...; mt="$(perl -e '...')"; done | sort ...)" tripped a bash parser quirk (apostrophes inside # comments inside the outer $(...)) — a clean bash -n would unexpected EOF while looking for matching '. Extracted the find/while pipeline into a _claude_usage_find_recent_jsonl helper so the outer command substitution stays a single function call, and dropped the bare apostrophes from comments. Without this fix, the hook fails to run on commit (so no JSONL record is ever written).
  • tests/test_git_hook.py: compared paths against Path(self.repo).resolve() instead of str(self.repo). On macOS tempfile.mkdtemp() returns /tmp/... but git rev-parse --show-toplevel resolves the /tmp -> /private/tmp symlink, so the raw equality check failed on every Mac.
  • Restored .github/PULL_REQUEST_TEMPLATE.md (the branch had accidentally deleted it).

Tracks which Claude Code sessions led to which commits.

- hooks/post-commit: bash script that appends {repo, sha, message,
  author, timestamp, session_id} to ~/.claude/git-trace.jsonl after
  every commit. Self-detects session_id via $CLAUDE_SESSION_ID env
  or the most-recently-modified JSONL in ~/.claude/projects/.
- cli.py: 'install-git-hook' installs per-repo (default) or --global,
  sets core.hooksPath, and ships the hook into the repo as a visible
  file (not hidden inside .git/hooks/).
- dashboard.py: _load_git_trace() reads the JSONL, the field
  'git_trace_recent' (last 50) is included in /api/data, and an
  insight card on the dashboard shows the top sessions producing
  commits in the last 7 days.
- tests/test_git_hook.py: 14 tests across hook script, hook execution
  against a temp repo, the installer CLI, and the loader.
@jakduch jakduch force-pushed the feat/git-hook-integration branch from b786834 to bbe7e18 Compare May 24, 2026 19:56
@josepe98 josepe98 closed this May 25, 2026
@josepe98 josepe98 reopened this May 25, 2026
@josepe98 josepe98 closed this May 25, 2026
@josepe98 josepe98 reopened this May 25, 2026
@josepe98 josepe98 closed this May 25, 2026
@josepe98 josepe98 reopened this May 25, 2026
@josepe98 josepe98 closed this May 25, 2026
@josepe98 josepe98 reopened this May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants