Integration
branch-tally’s repository is also a Claude Code plugin marketplace. It offers two optional plugins that run branch-tally from Claude Code hooks, so the numbers keep working after Claude Code purges old transcripts and after worktrees are removed.
| Plugin | Runs on | Runs | Gives you |
|---|---|---|---|
branch-tally-capture | SessionStart, SubagentStop, Stop | branch-tally capture | A record of which branch each session was on, so calls whose transcript stamp is missing can still be attributed. See Hook captures. |
branch-tally-record | SessionEnd | branch-tally record | The ledger filled in at the end of every session, before the transcripts can be purged. |
They are separate plugins so you can install, disable or remove each one on its own. Claude Code can turn off a whole plugin but not one hook inside it.
Two more integrations are planned and not available yet:
- A Claude Code skill to record and publish a pull request’s usage. Tracked in #11.
- A GitHub Action for agent workflows. Tracked in #12.
Until they ship, publish the table yourself with branch-tally report --pr. See the
CLI reference.
Before you install
- Install the binary first. Both plugins call
branch-tallyby name, so it must be on thePATHthat Claude Code’s hooks see. See Installation. Without it the hooks do nothing. - Start Claude Code inside the repository. The hooks run in the session’s working directory. In a directory that is not a git checkout they find nothing to record.
Install
In Claude Code, add the marketplace once, then install the plugins you want:
/plugin marketplace add IAmSkweetis/branch-tally
/plugin install branch-tally-capture@branch-tally
/plugin install branch-tally-record@branch-tally
Install either one, both, or neither. To stop one later, disable or uninstall it:
/plugin disable branch-tally-record@branch-tally
/plugin uninstall branch-tally-record@branch-tally
What the hooks do
flowchart LR
S[SessionStart, Stop,<br/>SubagentStop] -- capture plugin --> C[".tally/captures.jsonl<br/>(main checkout)"]
E[SessionEnd] -- record plugin --> L[".tally/ledger.jsonl"]
C -. "read by report, record" .-> R[report]
L -. "report --from-ledger" .-> R
branch-tally-capture runs branch-tally capture when a session starts, when a subagent
finishes and when the main agent stops. Each run appends one line to .tally/captures.jsonl in the
repository’s main checkout: the session id, the agent id for a subagent, the branch, the kind of
event and the time. Hook captures covers the file and how reports use it.
branch-tally-record runs branch-tally record when a session ends. That appends the current
branch’s attributed usage to .tally/ledger.jsonl, exactly as running record by hand would:
- It records the branch that the session’s directory has checked out when the session ends, in the
attribution mode from your config file (
claimby default). - The ledger it writes is at the root of the checkout the session ran in. A session in a git worktree writes the worktree’s own ledger, which merges back with the worktree’s branch.
- It writes the file only. It does not
git addor commit. Commit the ledger yourself, as described in Persisting the Ledger. - It is safe to run every session.
recordappends only the calls the ledger does not already hold.
The hooks never block a session
Every hook command ends in ; exit 0, for example branch-tally capture; exit 0. That works in
both Bash and PowerShell, and it means a hook always exits 0, whatever happened: a missing binary,
a directory that is not a repository, a detached HEAD, a branch with no usage yet, or a usage
error. Claude Code never sees a failure and never blocks Stop or SubagentStop.
The three capture hooks each set a 30-second timeout. The record hook sets none, because a
plugin can’t change how long SessionEnd gets. That limit is covered next.
SessionEnd time limit
Claude Code gives SessionEnd hooks 1.5 seconds by default. A timeout on a hook that a plugin
provides does not raise that budget. Only a timeout in your own settings files, or an environment
variable, does. branch-tally record reads all of the repository’s transcripts, so a long history
can take longer than 1.5 seconds. To give it more time, do one of these:
- Set
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS, in milliseconds, before you start Claude Code. For example,CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=10000 claudeallows 10 seconds. This needs Claude Code v2.1.268 or later. Before that, the variable raised only the overall budget, and a hook with notimeoutof its own, like the record plugin’s, was still stopped after 1.5 seconds. - Add your own
SessionEndhook that runsbranch-tally record; exit 0with atimeout(up to 60 seconds) in your settings file, instead of installing the record plugin.
These rules belong to Claude Code, not branch-tally. Check the SessionEnd section of the
Claude Code hooks reference for the current behaviour.
If Claude Code stops record before it finishes, the usage it missed can still be recorded.
record appends only the calls the ledger does not already hold, so the next SessionEnd, or a
branch-tally record you run by hand, writes the rest, provided it runs before the transcripts are
purged. If the stop cut the ledger’s last line short, reports skip that
line, count it as malformed in a note: skipped N ledger line(s) message on stderr, and carry on.
The next record starts on a fresh line and writes the cut entry again whole. Until it does, a
report from the ledger is missing that call. The total is then lower than the real cost, but the
report does not label it “at least” for this reason: the stderr note is the signal.
Which shell runs the hooks
Claude Code runs each hook command through a shell: sh -c on macOS and Linux, Git Bash on
Windows, or PowerShell on Windows when Git Bash is not installed. ; exit 0 works in all of them.
If you copy a hook into your own settings, its shell field ("bash" or "powershell") picks one
explicitly.
An error can still print a line on stderr. branch-tally capture prints one short fixed message
and never echoes the payload or the working directory.
Privacy
The hooks read only the hook payload Claude Code passes on stdin. capture reads its session id,
event name, agent id, agent type and working directory, and ignores every other field. It stores
no path: it asks git which branch the working directory is on and keeps only the branch name.
record stores what the ledger stores. Neither writes message
content.
Both files are designed to be committed, so anyone who can read the repository can read the session ids, agent ids and branch names in them.