Skip to content
claudemods

Claude Code Tool Call Counter with /tally

Counts Claude's tool calls per turn and session, shows the count by the spinner, and adds /tally for a breakdown.

Permissions

  • Intercepts tool callsSees every tool call Claude makes (tool.call / tool.check) and could block or change it.
  • Changes the UIDraws into the Claude Code interface: bands, panes, status text, toasts (ui.render / $.ui.*).
  • Adds commands/toolsRegisters new slash commands or tools that you or Claude can invoke ($.command.register / $.tool.register).
tested with
v2.1.291
last tested
requires
Claude Code ≥ 2.1.287
surfaces
spinner, transcript, status

Install inside Claude Code:

/plugin install cm-tool-call-counter --marketplace rotbit/claudemods-marketplace
All install options ↓

What does this mod do?

Tool Call Counter is a Claude Code tool call counter. While Claude works, the spinner line gets a suffix with this turn's count, for example · 7 tools, so you can see how busy a turn is without scrolling the transcript. Type /tally at any time for a table of the session so far:

**Tool calls** · this turn: 7 · this session: 41 (2 errors)

| Tool | Calls | Errors |
| --- | ---: | ---: |
| Bash | 18 | 2 |
| Read | 15 | 0 |
| Edit | 8 | 0 |

It is useful when you want to know why a turn took long, whether Claude is re-reading the same files, or how often its shell commands fail. Optionally, the status line keeps the running total: 🔧 41 tool calls (2 errors). Requires Claude Code 2.1.287 or later.

Demo

The recording asks Claude for a task that needs several tools. The spinner suffix counts up as calls are made, and running /tally afterwards prints the per-tool table.

Install

Install with one of the methods in the install block. /tally is registered when the session starts.

$ install cm-tool-call-counter

Requires Claude Code ≥ 2.1.287

  1. 1.One line, inside Claude Code

    /plugin install cm-tool-call-counter --marketplace rotbit/claudemods-marketplace

    Paste into a running session (v2.1.275+). Claude Code asks to add the marketplace first.

  2. 2.From your shell

    claude plugin marketplace add rotbit/claudemods-marketplace
    claude plugin install cm-tool-call-counter@claudemods

    Then run /reload-plugins in any session that is already open.

  3. 3.Try it without installing

    git clone https://github.com/rotbit/claudemods-marketplace
    claude --plugin-dir ./claudemods-marketplace/cm-tool-call-counter

    Loads the mod for one session only. Nothing is added to your settings.

How it works

The counting happens in one tool.call hook that observes every tool. From hooks/register.ts:

on('tool.call', async ($, e, next) => {
  const tool = String(e.tool)
  await update($, turn, t => addCall(t, tool))
  const now = await update($, session, t => addCall(t, tool))
  if (showInStatus) $.ui.status(statusText(now))

  const ran = await next(e)

  if (ran.deny !== undefined || ran.isError === true) {
    await update($, turn, t => addError(t, tool))
    const failed = await update($, session, t => addError(t, tool))
    if (showInStatus) $.ui.status(statusText(failed))
  }
  return ran
}).catch(($, e, next) => next(e))

on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
  if (!showInSpinner) return next(e)
  const { count } = await read($, turn)
  if (count === 0) return next(e)
  return next({ ...e, props: { ...e.props, suffix: spinnerSuffix(e.props.suffix, count) } })
})

A call is counted as it starts, in two $.state atoms: turn, reset by a turn.start hook, and session. The hook then passes the call on unchanged with next(e) and, if the result was denied or flagged isError, records an error. The .catch runs the call anyway if counting throws, so the mod can never block a tool.

The ui.render hook for the Spinner site rewrites only the suffix prop and passes everything else to next, so Claude Code still draws its own spinner. Because the count is in $.state, the spinner redraws as soon as the number changes. /tally is registered in session.start with $.command.register, and a command.run hook for tally returns { text } built by formatTable in hooks/tally.ts.

Customize it

Change these options in /config. The spinner wording comes from spinnerSuffix and the status text from statusText, both in hooks/tally.ts. To sort the /tally table differently, edit the sort call in formatTable.

SettingTypeDefaultWhat it does
showInSpinnerbooleantrueShow in spinner. Append " · N tools" (this turn's count) to the working spinner.
showInStatusbooleanfalseShow in status line. Also show the session's total tool calls and errors in the status line.

Change these in Claude Code with /config — the mod hot-reloads.

Permissions & safety

Intercepts tool calls appears because the mod hooks tool.call for every tool. It only observes: it never denies or modifies a call, and it does not look at arguments beyond the tool name. Changes the UI covers the spinner suffix and the optional status text. Adds commands/tools covers /tally.

It runs no commands, reads no files, uses no network and stores nothing between sessions. Disable it with /plugin disable cm-tool-call-counter, or start a session with every mod off using claude --safe-mode.

Intercepts tool calls
Sees every tool call Claude makes (tool.call / tool.check) and could block or change it.
Changes the UI
Draws into the Claude Code interface: bands, panes, status text, toasts (ui.render / $.ui.*).
Adds commands/tools
Registers new slash commands or tools that you or Claude can invoke ($.command.register / $.tool.register).

Compatibility & troubleshooting

Tested with Claude Code 2.1.291 in the terminal on macOS. If you see no count:

  • Check claude --version is 2.1.287 or later and the mod is enabled in /plugin.
  • Run /reload-plugins after a shell install; /tally appears once the session restarts the mod.
  • The suffix only shows while a turn has made at least one tool call.
  • showInSpinner may be off in /config.

Counts cover the time since the mod loaded in this process, so they reset after a reload.

FAQ

Does the tool call counter change or slow down tool calls?
No. It counts the call, lets it run unchanged with next(e), and counts an error afterwards if the call failed. If counting itself fails, the .catch handler still runs the call.
What counts as an error in /tally?
A call whose result comes back with isError, or one that another hook denied, for example a safety mod refusing a Bash command.
Does the session count survive a restart?
No. Counts live in $.state, which lasts for the current Claude Code process. A new session or a mod reload starts from zero.
Does /tally call the model?
No. The command.run hook answers it directly from the counts the mod collected, so it is instant and uses no tokens.
Can I see the total without opening /tally?
Turn on showInStatus in /config to keep the session total and error count in the status line.
Context Meter running in Claude Code

Context Meter

Context-window fill, 5-hour and 7-day plan usage and session cost in the status line, plus a warning near the limit.

  • status
  • toast
  • Changes the UI

tested with v2.1.291

Block Dangerous Commands running in Claude Code

Block Dangerous Commands

Refuses rm -rf /, force-push to main, DROP TABLE, curl | sh and other destructive Bash commands before they run.

  • toast
  • Intercepts tool calls
  • Changes the UI

tested with v2.1.291

Git Branch Above Prompt running in Claude Code

Git Branch Above Prompt

Shows your git branch, changed-file count and ahead/behind status in a one-line band above the Claude Code prompt.

  • above-prompt
  • Runs commands
  • Intercepts tool calls
  • Changes the UI

tested with v2.1.291

More in Slash commands, Context, quota, cost, Status line & prompt bar.