Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

PluginRuns onRunsGives you
branch-tally-captureSessionStart, SubagentStop, Stopbranch-tally captureA record of which branch each session was on, so calls whose transcript stamp is missing can still be attributed. See Hook captures.
branch-tally-recordSessionEndbranch-tally recordThe 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-tally by name, so it must be on the PATH that 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 (claim by 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 add or commit. Commit the ledger yourself, as described in Persisting the Ledger.
  • It is safe to run every session. record appends 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 claude allows 10 seconds. This needs Claude Code v2.1.268 or later. Before that, the variable raised only the overall budget, and a hook with no timeout of its own, like the record plugin’s, was still stopped after 1.5 seconds.
  • Add your own SessionEnd hook that runs branch-tally record; exit 0 with a timeout (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.