ClaudeMod
Back to browse
Mods

cctop

A btop-style live dashboard pane for Claude Code showing context fill, token counts, cost, cache hit ratio, and subagent latencies.

Tom Stagl4 starsAdded 1 day ago

cctop

See what Claude Code is doing — live, in a pane beside it.

Releases · PRD · build plan · brand · MIT


cctop is an htop/btop-style terminal dashboard for a running Claude Code session. It shows the internals Claude Code doesn't — context fill and when the next compaction hits, tokens and cost with cache-hit ratio, rate limits with an exhaustion forecast, what the current turn is waiting on, per-tool latency and how much context each tool pushed, subagents and MCP servers, touched files — on one page, in real time, in a right-hand split while you keep working on the left.

Type /cctop in a Claude Code session and it appears — docked inside Claude Code as a panel when the build supports it, otherwise attached as a terminal split beside it. One command either way; see Two ways to see it.

What it looks like

 cctop  claude-sonnet-5 · turn 6 · 9h 25m · ENDED 23:28 · /home…  ● COMMITTING 8h 40m
 1: ctx 14% · 425k left     2: 5h — · no status line   3: cache 59m≈ · 1h TTL
 4: spend $18.7 · ≈$1.01/h  5: ✓ python3 -… 9h01 · re… 6: 140 calls · 6 err
 ▸ `cd lorem_ipsum_dolor_sit_amet…` blocked the turn… — queue: 'run       a: advisor
   builds and test suites longer than a …  LATER · turn 6
─── events ─────────────────────────────────────────────────── 0: home  ·  ? keys ───
  23:29  api     cost-state $18.75 · api 22:14 · retries 0:00
  23:29  note    /clear · continued in a new session
  23:28  coach   LATER A10 fired · `cd lorem_ipsum_dolor_sit_amet…` blocked the turn…
  23:28  tool    Bash ✓ 49
  23:28  tool    Bash git commit -m "$(cat lorem_i lorem lore lor lorem_i lorem_ip l…
  23:27  tool    Bash ✓ 16
  23:27  tool    Bash git push lorem_ lore 2>&1 ▶
  23:27  tool    Bash ✓ 32
  23:27  cost    model opus-5 → sonnet-5
  23:27  tool    Bash git commit -m "$(cat lorem_i lorem_ipsu lorem_i lore cctop lor…
  23:25  tool    AskUserQuestion ✓ 83
  14:49  tool    AskUserQuestion ▶
  14:49  api     API error: invalid_request 400
  14:49  compact compacted auto 567k → 230k in 1:20
  14:48  note    interrupted after 6 calls
  14:47  api     API error: request failed
  14:46  tool    TaskUpdate ✓ 5
  14:46  tool    TaskUpdate completed ▶

Claude Code keeps running in the left pane; cctop attaches to it from the right. A header that never moves and one body that fills the rest: the identity line with the phase cell (● COMMITTING 8h 40m) at the right; six cells — context, limits, cache, spend, work, tools — three per row from 80 columns and two below, each a target whose digit opens its body in place; the act line, the coach's slot, wrapped rather than cut, a for the advisor; the rule line naming the open body, 0 the way home, ? its key map; then the body — 1 above opens the context body, the five slices of the window as bars in order of what you can do about them, the counters and what the light says; 4 the spend with its provenance and the token mix; 6 the by-tool table with the agents and the team. Enter opens the body's full-screen panel (Esc back), and inside a panel the digits 1–9 still switch panels: inside panels 1 and 2 Enter opens the turn ledger; inside panel 6 it opens the agents view — one row per subagent with its model, time, tokens, priced cost, what came back (ret) and what was wasted, with the reason (failed, killed, no ret, idle), workflow runs folded into one row each, and the team the session leads as a second group — one row per teammate with its context, tokens, its own cost (Claude Code's own figure once it ended) and turns — sorted with s/S. Press c for the coach view: the same four lights as a 56-column card with the nudge, what is next and what is snoozed.

Two ways to see it

/cctop picks one automatically — it never asks you to choose.

Terminal view. The dashboard above, running as its own process (cctop run) in a split of your terminal multiplexer (tmux, zellij, WezTerm, Kitty, iTerm2). This is what /cctop falls back to, and what you get from cctop split directly. See Install & attach.

Panel view. On a Claude Code build with function hooks enabled, /cctop docks the same dashboard inside Claude Code, above the prompt, drawn in Claude Code's own frame and colour style — no multiplexer needed. Its Overview is Console above, row for row — the cells, the act line and 0: home are the engine's own clickable chrome, the digit each draws being its hotkey; its Coach view is the card the TUI's c shows, with buttons:

╭coach ─ opus-5 · turn 5 ──────────────────────────────────╮
│ PLANNING · 5c +490 · silent 13m · ▸ steer window         │
│ ──────────────────────────────────────────────────────── │
│ ◐ context  720k ▇▇▇▇▇▇▇▁▁▁ 72% · ≈$.37/call              │
│ ○ cache    warm 1h00 (1h) ≈                              │
│ ○ limits   — no status line                              │
│ ● rework   4 blocked · edits 3 ✓ none 18m                │
│ ──────────────────────────────────────────────────────── │
│ ▸ rm is denied by your rules                             │
│   tell Claude the alternative — it cannot run this       │
│   NOW · fired at call 6 · +4 queued (n)                  │
│                                                          │
│ next     context-reset → next-row only · ctx 720k →…     │
│ snoozed  —                                               │
╰──────────────────────────────────────────────────────────╯
[1 fill] [2 snooze] [3 why]
╭● rework ─ transcript ────────────────────────────────────╮
│ last check `python3 - lorem_i lorem…` ok 19m ago         │
│ fails: Denied 4 · Other 2                                │
│ rewind 1 checkpoints this turn                           │
╰──────────────────────────────────────────────────────────╯
[◐ context] [○ cache] [○ limits]  ● rework

The view bar switches between the Overview, the Coach, Tools, Agents, Files, Events and the Advisor, the same way c/s/f/p work in the terminal view. The Coach view is the same 56-column card as the TUI's c view — the state line, four lights, the one nudge — with [1 fill] (writes a prompt-class action into the prompt box; nothing is ever submitted), [2 snooze] and [3 why], a detail frame that follows the highest light, and the coach's one-line form pinned under the prompt. It needs "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1" in the env block of ~/.claude/settings.json and /tui fullscreen; cctop pane status tells you which prerequisite is missing. The /diff panel and the cctop panel share one dock, so hide one to see the other — see docs/claude-code-panels.md. Falls back to the terminal view automatically when function hooks are off.

Bodies

The nine panels of the terminal view collapse into eight bodies on Console; every panel stays reachable with Enter.

keyBodyAnswersEnter opens
1contextHow full is the window, what it is made of and which part you can move, how many turns until autocompactpanel 1 Context
2limits5 h and 7 d usage as meters, reset countdown, the model's weight, will I run out before the resetpanel 3 Limits
3cacheThe countdown while the entry is warm, misses, the hit ratio, the re-write at stake if it goes coldpanel 2 Tokens & Cost
4costThe session's spend with its provenance (ledger, since, agents, team), the token mix, burn rate, the cost of continuing, who spent itpanel 2 Tokens & Cost
5workThe last check, the rework light, files touched and re-read, git, the turn's counterspanel 7 Files
6toolsCalls, errors, p50 and tokens each tool pushed into context; what is running; the agents, the team, MCP serverspanel 5 Tools
aadvisorThe nudge whole — headline, action, evidence, class — what is next and what is snoozedthe coach view
0eventsTool / hook / permission / compaction / coach / note stream — the way homepanel 8 Events

The Advisor is rule-based (36 rules today, no model call). On the token axis: named cache misses, cache expiry, the cache countdown while a question waits, runaway tool results, re-reads, exploration runs in the main context, post-compaction re-triggers, idle MCP servers and plugins, thinking share, permission waits, long foreground commands, pasted input, chatty turns, rate-limit pacing, subagent model choice, agents whose work did not come back, hook overhead, oversized prefix, a warm model switch, a cold resume, the context cost past 200 k, an armed loop. On the outcome axis: the turn that died on an API error, Claude waiting on you, a failure cascade, a denial streak (with the allow rule), a correction streak (Esc Esc), a commit without a check, source edits with no test run, a natural boundary to /clear at, a PR without a review pass, destructive git on a dirty tree, an IDE/Claude edit collision; plan-first and long-context drift sit in the next row only. Every trigger is structural (a tool result, a denial kind, an interrupt marker, an API-error line, a git operation), never a keyword in your prompt. Its engine keeps one nudge in a slot by class (NOW › NEXT › LATER), with hard TTLs, cooldowns, an acted predicate per rule and persistent snoozes (x five turns, X the session), and the coach view (c) shows that slot beside four lights: context, cache, limits, rework.

The coach measures itself. Every fire is recorded in ~/.cctop/<session>.advisor.json (rule, class, the surface that showed it, idle time at fire, the snooze delay, acted or expired, version, model, project); cctop run --coach auto alternates sessions between an exposed arm and a control arm — fires still recorded, nothing shown — within each project / model family / version, and cctop coach-stats [--since 4w] [--replay ~/.claude/projects] prints per rule the exposed fires, acted, snoozed, reflex dismissals (x within 2 s), toggle-aways, expired-unacted, the control arm's acted-anyway rate, the false-positive rate and the causal lift, with the verdict: a rule past 20 % false positives after ten exposed fires is demoted to the next row, a precision collapse on a new Claude Code version to LATER, both applied at the next attach. The same command reports what the coach costs (socket sends, CPU, git shell-outs, hook latency). The first turn of a session shows one dim line from Claude Code's own /insights analysis of this project (medians, satisfaction, the top friction), counts and verdicts only.

Some numbers moved with the coach work: the turn count is Claude Code's own (promptId; interrupts, slash commands and task notifications no longer count, so it reads ~15 % lower than before), API-error lines no longer set the model or count as a compaction, compactions come from the compact_boundary records Claude Code writes since 2.1.263, and the autocompact threshold is the effective window − 13 000 tokens (967 k on 1M-window models) rather than 80 %.

How it works

Read-only. No changes to Claude Code. Data comes from what Claude Code already writes:

  • ~/.claude/sessions/*.json — which sessions exist and whether they're busy
  • ~/.claude/projects/<cwd>/<session>.jsonl — the transcript: per-response usage (deduplicated by message.id), tool calls and results, turn durations, hook timings, Claude Code's own cost-state
  • …/<session>/subagents/ — subagent transcripts: each agent's own calls (a message's last streamed line, a fork's replayed parent message skipped), priced into the headline cost beside the main transcript's — the cost-state already holds the agents' earlier calls, so only the calls after it are added — and, with the <task-notification> that returned each agent's result, what came back and what was wasted (cost_combined, agents_waste in the reference below; cctop query summary keeps cost main-only for one release)
  • ~/.claude/teams/<team>/config.json and the teammates' own transcripts (…/<cwd>/<session>.jsonl, matched by the teamName on their lines) — when the session leads an agent team: each teammate's context, tokens, turns and its own cost-state, exact once it ended and priced while it runs, folded into the headline cost as team ≈$X (N of M read) and listed as a second group in the agents view; the team directory goes when the team ends and the transcripts stay, so a finished session still shows its team (team_cost, teammate_cost below)
  • the status-line JSON (via an optional shim) — context size and rate limits
  • hooks (optional) — exact tool timings, permission prompts, compactions
  • the process tree — running commands, MCP servers, memory

/clear starts a new transcript under a new session id in the same Claude Code process; the dashboard notices within 2 s and re-attaches to the new session (a toast says so), and cctop query --session <old id> still answers from the old transcript as an ended session.

Everything on screen is defined once in a metrics registry (src/metrics/registry.rs) that generates docs/metrics.md and the reference below; CI fails if either drifts.

Header

MetricUnitHow it is computedSourcesCaveatsEstimate
Status session_statusenumstatus from the session registry (busy/idle); WAITING when a permission request is pending; ENDED when the pid is goneD1 D4—never
Turn turn_numbercountPrompts the person wrote so far, one per promptId (promptSource typed / suggestion_accepted / queued, or origin.kind human); interrupts, slash commands, task notifications, teammate messages and the compaction summary are not turnsD2A resumed session starts counting at the resume point; before Claude Code 2.1.220 every non-meta text line countsnever
Turn elapsed turn_elapsedmsturn_duration.durationMs once the turn ended, else now − turn startD2Claude Code writes turn_duration per attempt and re-drives the same prompt (after /login) with no new user line: a response after it reopens the turn, which is live again until the next one. Panel 4 reads the turn; the header's phase word is the classifier over the last calls, and the two are labelled as suchnever
Effort effortenumperTurnEffort of the latest assistant line when set, else its effort, else the status line's effort.level; with thinking on/off and fast mode from the status lineD2 D3—never
Plan plan_tierenumoauthAccount.userRateLimitTier (else organizationRateLimitTier) from ~/.claude.jsonD12The status line never carries a plan; keys are read, never the account's namesnever
CPU process_cpu%CPU share of the claude process over the last sample intervalD5—never
Memory process_rssbytesResident set size of the claude processD5—never

Context

MetricUnitHow it is computedSourcesCaveatsEstimate
Context size context_sizetokenscache_read + cache_write + input of the turn's last API call — everything the model readD2 D3The status line's total_input_tokens is preferred when the shim is installedest when computed from the transcript alone
Context window context_windowtokenscontext_window_size from the status line, else the model's default windowD3—est without the status-line shim
Fixed prefix context_prefixtokenscache_read + cache_write of the session's first API call: system prompt, CLAUDE.md, tool schemas — lowered to the context right after any boundary that lands below it, and replaced by the /context table's own categories (all but Messages) when the person ran one and no model switch followedD2With a warm cache the first call is a read, so both fields are summed. The first call also carries the opening message and its attachments, so the figure overstates the fixed part (+33 % on the one ground truth); a /context run corrects it≈ until a /context has run
Context sources context_sourcestokensOne row per kind of content in the window since the last boundary: prefix · files · bash output · mcp results · agent returns · web · other results · prompts · harness · thinking · tool inputs · prose · other. Results, inputs and prompts are chars / 4 placed on the API call that first carried them and reconciled per step against Δcontext − previous output_tokens; thinking and prose are exactD2The rows sum to the context size by construction, so the sum is not the test — overflow_raw (what the estimates would have exceeded the size by with no reconciliation) and reconciled (what came off) are. The inspector opens with m on the Context panel≈ on every row but thinking and prose
Tokens by source source_tokenstokensΣ tokens_to_ctx of the calls whose results a step carried, by kind: Read and a Bash cat / sed -n / head / tail of exactly one path → files; other Bash → bash output; mcp:* → mcp results; Agent → agent returns; WebFetch / WebSearch → web; the rest → other results. Prompts: prompt_chars / 4 + 1 500 per pasted image. Harness: the attachments' tokensD2Scaled down on a step whose estimates exceed its exact growth; a Bash command that reads two or more files stays in bash output≈
Per-file tokens file_tokenstokensA file's Read results in the window (read: in the files row) and the bytes the model wrote into Edit / Write of it (written: in tool inputs), with its read countD2Relative Bash paths resolve against the session's cwd; a file read before the last boundary shows nothing — its content left with it≈
Context reference context_referenceenumWhere the window's accounting starts: session-start, or the last boundary's kind with the index of its first call and the Δcontext that opened itD2A model switch that did not shrink the window keeps the rows in the old model's tokens and is reported as model_switch_keptnever
Context velocity context_velocitytokens/turnExponential moving average (α = 1/5) of Δ context size per turnD2Turns that compacted are excluded from the average; — until a second turn has made a call (no sample is not zero growth)never
Turns until autocompact turns_until_compactionturns(autocompact threshold − context size) / context velocityD2 D3Threshold = Claude Code's effective window − 13 000 tokens, the effective window being the nominal one less a 20 000-token output reserve (967 000 on native-1M models, 167 000 on 200 k windows) until a compaction has been observed for the model, then the observed value is usedest until a compaction has been observed
Context anatomy context_anatomytokensThe stacked bar: prefix · tool inputs (chars the model wrote / 4, capped per call at its output less its thinking) · tool results (tokens_to_ctx, each placed on the API call that first carried it) · retained thinking (exact) · harness (attachments, each placed on the call that first carried it) · prose (exact: output − thinking − inputs per call) · unattributed (prompts and the rest of the size), since the last boundary. Every step's estimates are reconciled against that step's exact growth, Δcontext − previous output_tokens, before they are summedD2A boundary is /clear, a compaction, a microcompact, a resume or fork, a ≥ 30 % drop with no marker, any smaller drop with no marker, or a model switch that re-measured the window smaller; the in-flight call and results after it are not resident. Encoding: the slices are drawn in order of agency and coloured by the theme's series ramp derived from its accent — dim for what cannot change this session (prefix, harness), mid for what the next boundary drops (thinking), bright for what the person can move (inputs, results, prose) — never by the status palette, and adjacent slices alternate the fill glyph so the order survives 16 colours, NO_COLOR and the pane≈ on every slice but thinking and prose
Harness per turn harness_tokenstokensAttachment tokens (reminders, injected files, listings) ÷ human turns since the last boundaryD2rendered[].content since Claude Code 2.1.266; per-subtype ratios before≈ before 2.1.266
Context band context_bandenumok below threshold − 20 000 · warn inside that band (Claude Code's footer turns to "Context low") · blocked at the threshold or window − 3 000; the footer text is Claude Code's own (N% until auto-compact, N% context used when autocompact is off); precompute armed at 80 % of the windowD2 D3 D12Overrides come from settings.json and the claude process environment (CLAUDE_CODE_AUTO_COMPACT_WINDOW, CLAUDE_AUTOCOMPACT_PCT_OVERRIDE, DISABLE_AUTO_COMPACT, autoCompactWindow, autoCompactEnabled)never
Compactions compactionscountsystem/compact_boundary lines (exact: trigger, pre/post tokens, duration), or a PreCompact hookD2 D4API-error lines (<synthetic>, zero usage) never countBefore Claude Code 2.1.263 (no compact_boundary) a drop between two API calls is a compaction when the compaction summary or a /compact sits between them, or when it is ≥ 30 % on the same model with no /model between (a model switch or a handover re-measures the window)

Tokens & Cost

MetricUnitHow it is computedSourcesCaveatsEstimate
Cache read cache_readtokensΣ cache_read_input_tokens over distinct API responsesD2Counted once per message.id; Claude Code writes one line per content blocknever
Cache write cache_writetokensΣ cache_creation_input_tokens, split into 5-minute and 1-hour TTL from cache_creation.ephemeral_*D2—never
Fresh input fresh_inputtokensΣ input_tokens (uncached)D2—never
Output outputtokensΣ output_tokensD2—never
Thinking thinkingtokensΣ output_tokens_details.thinking_tokens (a subset of output)D2—never
Cache hit ratio cache_hit_ratioratiocache_read / (cache_read + cache_write + fresh_input)D2Green ≥ 0.8, amber ≥ 0.5, red belownever
Cache TTL cache_ttlenumprompt_cache.ttl from the status line; else 1h if the latest call reports ephemeral_1h_input_tokens > 0, else 5mD3 D2—≈ without the shim
Cache warm cache_warmboolprompt_cache.warm from the status line; else whether the last API call is younger than the observed TTLD3 D2—≈ without the shim
Cache expires in cache_expires_inmsprompt_cache.expires_at − now, clock-driven between status rewrites; else last API call + observed TTL − nowD3 D2The status file is rewritten only at expiry, so the countdown runs on cctop's clock; the shim's figure is ignored when the file predates the last assistant line≈ without the shim, or when the status file is stale
Re-cache if cold cache_recache_if_coldtokensprompt_cache.recache_tokens_if_cold: what the next call re-writes if the cache expires firstD3—never
Cache misses cache_missescountprompt_cache.misses with miss_causes (model_changed, tools_changed, messages_rewritten, ttl_expired_1h/5m, likely_server_side…) and expected_rebuildsD3Claude Code counts a miss when the cache read is < 95 % of the input and ≥ 2 000 tokens were re-processednever
Cost costUSDClaude Code's cost-state.totalCostUSD, the latest per writing process (startTime: a --resume beside the live session or a bridge appends a second process's own running total to the same file) summed, plus a priced estimate of responses newer than the last ledgerD11 D2 D9Subscription plans have no per-token bill; the figure is the API-equivalent list price. A ledger that covers less than the transcript's own responses are worth is another process's (this one's is still to come) and does not displace the estimate — so two readers of one file never disagree by a process, and a $0 ledger from a process that did nothing never zeroes a busy session≈ when any part is estimated, or while the ledgers cover less than the responses
Combined cost cost_combinedUSDThe session's whole spend: cost-state.totalCostUSD (every process's, as cost; it already holds the subagents' calls and calls no transcript shows), plus the priced main responses after it, plus the priced subagent calls whose line timestamp is after the ledger's moment (the last timestamped line before the cost-state); with no cost-state, every main and agent call priced. Panel 2's headline, dashboard row 2, cctop report and cctop query's cost_combinedD2 D2a D9 D11Never a subtraction: main and agents are not derived from the ledger. A fork's replayed parent message is not priced. source says ledger / priced / mixed; cost keeps the main-only meaning for one release≈ when any priced part is non-zero
Cost by model cost_by_modelUSDcost-state.modelUsage[*].costUSD plus estimates per modelD11 D9—≈ when any part is estimated
Cost per call cost_per_callUSDcontext × cache-read price + median output × output price, at the current context; at the cache-write price of the observed TTL when the cache is coldD2 D3 D9What the next API call costs, not what the last one did≈ (always priced from the table)
Cost per turn cost_per_turnUSDcost per call × the session's own median calls per turn (turns with ≥ 1 call), also given at 100 k of context; next 30 calls = cost per call × 30D2 D9The median is the session's, never a constant≈ (always priced from the table)
Where the tokens went attributionratioInput tokens of API responses by their attributionSkill / attributionPlugin / attributionAgent / attributionMcpServer owner, machine-originated turns under idle, the subagents' own usage under agents; shares of all input tokensD2 D6A response without an attribution key is the person's own worknever
Agents cost agents_costUSDPriced usage of every subagent transcript (incl. subagents/workflows/**) and its share of the session's totalD2a D9—≈ (priced from the table)
Team cost team_costUSDΣ over the teammates of the team this session leads, each from its own transcript: its cost-state.totalCostUSD where one exists, plus its priced calls after that line (all of them while it has none); members found from ~/.claude/teams/<team>/config.json, the lead's teammate_spawned results and a scan of the transcripts naming the team (teamName == session-<lead id8>). Part of cost_combined, toggled with the agents by aD12 D2c D11 D9A teammate's ledger already holds its own subagents and hidden calls; never a subtraction. A member whose transcript is not found adds nothing and marks the sum≈ for a teammate's calls after its last cost-state, or all of them while it runs; ≈ and "N of M read" when a transcript is missing
Limit weight limit_weightratio/usage's weight of a call: (cached + uncached × 10 + cache-create × 12.5 + output × 50) × tier (fable 10, opus 5, sonnet 3, haiku 1)D2 D12Why the limit bar moves faster than dollarsnever
Behaviour flags behaviour_flags%/usage's five flags as shares of the weighted usage: cache_miss (requests with > 100 k uncached tokens), long_context (> 150 k context), subagent_heavy, high_parallel (≥ 4 live sessions), cron (active ≥ 8 h); shown with Claude Code's own tip text at ≥ 10 %D2 D1 D12—never
Burn rate burn_rateUSD/hCost of turns active in the trailing 15 minutes, scaled to an hour over the part of the window they coverD2 D9A turn counts from its start (clamped to the window) to its last line; windows shorter than 1 minute are treated as 1 minute≈ (always priced from the table)
Input rate input_ratetokens/minTotal input tokens of turns started in the trailing 15 minutes ÷ windowD2—never

Limits

MetricUnitHow it is computedSourcesCaveatsEstimate
5-hour usage limit_5h%rate_limits.five_hour.used_percentage from the status lineD3Account-wide: other live sessions contributenever
7-day usage limit_7d%rate_limits.seven_day.used_percentage from the status lineD3Account-widenever
Resets in limit_resetdurationresets_at − nowD3—never
Rate limited limit_hitenumThe newest API-error line with error: rate_limit (or status 429): quotaLimits.rateLimitType, resetsAt, lowPriorityRetryAfterSeconds — cleared by the next successful callD2Exact without the shim: Claude Code writes the 429 into the transcriptnever
Spend limit spend_limit%rate_limits.spend_limit.used_percentage from the status line, for accounts with a monthly limitD3—never
Other sessions other_sessionslistLive registry entries other than this one: busy/idle and how long (statusUpdatedAt)D1They share the rate limitnever
Projected exhaustion limit_exhaustiondurationLeast-squares slope of used_percentage samples over the last 30 min, extrapolated to 100 %D3Needs ≥ 3 samples; rate-limit units are plan-specific, so tokens are not used≈ always

Turn

MetricUnitHow it is computedSourcesCaveatsEstimate
Turn duration turn_durationmsturn_duration.durationMs system line written when the turn endsD2—never
API calls api_callscountDistinct message.ids in the turnD2—never
API time api_timemscost-state.totalAPIDuration for the session; per turn, gaps between a user/tool_result line and the next assistant lineD11 D2—≈ per turn
Retry time retry_timemstotalAPIDuration − totalAPIDurationWithoutRetriesD11—never
Phase phaseenumThe last call's phase over the last seven calls (phase.rs: EXPLORING / IMPLEMENTING / VERIFYING / COMMITTING / PLANNING / DELEGATING / BROWSING / OPS / WAITING) and its run length; WAITING when a permission dialog, an AskUserQuestion or a Notification is pendingD2 D4A test-class command is VERIFYING only once its output confirmed a runnever
Last check last_checkenumThe newest test-class Bash call whose output confirmed a run (test result:, N passed, # pass…), its verdict and age; edits since counts Edit/Write calls after itD2—never
Waiting on you waitingenumA pending permission dialog (hook), a running AskUserQuestion / ExitPlanMode, an idle_prompt / agent_needs_input notification, or a finished turn whose last text ended with ?; with the wait's durationD2 D4—never
Steers steerscountHuman queued_command attachments folded into the turn (absorbed mid-turn); task notifications are machine turns, not steersD2—never
Interrupts interruptscount[Request interrupted by user…] lines with interruptedMessageId, and the output tokens the cut turns had producedD2—never
Hook time by command hook_by_commandmsstop_hook_summary.hookInfos[].command and hook_success attachments summed per command over the session; preventedContinuation marks a blocked stopD2—never
Goal goalenumThe last goal_status attachment (/goal): met, iterations, tokensD2—never
Hook runs hook_runscountNumber of hookInfos entries in the turn's stop_hook_summaryD2Only Stop hooks are summarised by Claude Code; other hook events need cctop installnever
Hook time hook_msmsΣ hookInfos[].durationMs for the turnD2 D4—never
Permission wait permission_waitmsPermissionRequest → PostToolUse for the same tool_use_id, minus the tool's median durationD4PreToolUse fires before the prompt, so it cannot bound the wait≈ always
Queued prompts queued_promptscountqueue-operation enqueue − dequeue/remove; popAll resets to 0D2—never

Tools

MetricUnitHow it is computedSourcesCaveatsEstimate
Calls tool_callscounttool_use blocks per tool name; MCP tools grouped as mcp:<server>D2—never
Errors tool_errorscounttool_result blocks with is_error per toolD2—never
p50 duration tool_p50msMedian of tool_use → tool_result durationsD2 D4Transcript timings include any permission wait≈ until hook timings replace them
p95 duration tool_p95ms95th percentile (nearest rank) of durationsD2 D4—≈ until hook timings replace them
Last call tool_last_calldurationnow − the tool's most recent tool_use timestampD2—never
Tokens → context tokens_to_ctxtokensΣ len(result text) / 4 per tool, plus w·h/750 per image (1 500 when the size is unknown); cleared results count 0D2 D10Heuristic; exact with OpenTelemetry. Uses the text in the transcript, not offloaded tool-results/ files (their size is shown beside it)≈ without OTel
Input → context (IN→CTX) input_tokenstokensCharacters the model wrote as tool inputs / 4, per tool — they stay in context like results doD2Bash command text is the largest share≈ always
Bash by class bash_classcountBash calls by the phase classifier's class: explore / implement / test / build-lint / commit / gitread / ops / wait (Bash·test rows)D2—never
Error class error_classcountFailed calls by Claude Code's own taxonomy (Command Failed / User Rejected / Edit Failed / File Changed / File Too Large / File Not Found / Other) plus Content Not Found, Timeout, Tool Not Found and Denied (toolDenialKind)D2Classified from the result text, in Claude Code's ordernever
Top context consumers top_ctxtokensThe n single results with the largest tokens_to_ctx; ⊘ marks a result cut at a cap (truncatedByTokenCap, a persisted spill)D2—≈ without OTel
Re-read tax reread_taxUSDAPI calls since the result landed × its tokens × the cache-read price: what re-reading it has cost so farD2 D9—≈ always
ToolSearch loads tool_search_loadscountDeferred tools loaded through ToolSearch per MCP server (matches of its result); each load rewrites the cached prefixD2—never

Agents & MCP

MetricUnitHow it is computedSourcesCaveatsEstimate
Agent state agent_stateenumrunning while tool_uses are pending; done when the last response ends with text and no pending tool_use; failed when the last result is an error and nothing followed for 60 sD2a D4—never
Agent cost agent_costUSDThe agent's own calls priced from the table (a fork's replayed parent message excluded); — with the token count on a model the table does not knowD2a D9—≈ always
Agent status agent_statusenum<status> of the agent's <task-notification> (completed / failed / killed), by whichever of Claude Code's three deliveries it came — a user line, a queue-operation enqueue or a queued_command attachment; absent until it landsD2Claude Code's word beats the 60 s heuristic of agent_state where both existnever
Agent returned agent_returnedtokensLength of the notification's <result> ÷ 4 (a synchronous Agent result's content ÷ 4): what came back into the session; absent until a result exists, 0 for an empty oneD2A size, never a judgement of the result; the text is not kept≈ always
Agent waste agent_wasteUSDThe agent's priced cost under one reason, tested in order: failed (the notification, a workflow-journal failed entry, or the 60 s heuristic without a notification), killed, no ret (completed with an absent or empty result), idle (no notification, running, no line for 5 min, nothing in flight — no tool the hook spool saw start and not finish, none the transcript shows unanswered); a finished agent without a notification is not waste, and a notification's completed outranks the journalD2 D2a D4 D9Structural evidence only: statuses, lengths, counts, timings≈ always
Agents waste agents_wasteUSDΣ agent_waste by reason over every subagent, and the agents classifiedD2 D2a D4 D9—≈ always
Cold starts agents_cold_startscountAgents (forks excluded) whose first call wrote more cache than it read, and the cache-write dollars of those first callsD2a D9A cost of the design, not counted as waste≈ for the dollars
Return ratio agents_return_ratioratioΣ agent_returned ÷ Σ agent output tokens: the share of what the agents wrote that came backD2 D2a—≈ always
Workflow failed workflow_failedcountAgents of a workflow run with a failed journal entry, by phase, each with one cause from its last API-error line (apiErrorStatus, error token) and its call countD2aA 429 on the first call costs ≈$0: read it with the count, not the dollarsnever
Workflow failed $ workflow_failed_usdUSDΣ agent_cost of the run's failed agentsD2a D9—≈ always
Workflow waste workflow_waste_pct%Σ agent_waste of the run's agents ÷ the run's priced costD2 D2a D4 D9—≈ always
Workflow overhead workflow_overheadratioThe run's priced cost ÷ the main thread's priced responses between the run's start and its end (or now)D2 D2a D9A ratio, not a counterfactual: the main thread would not necessarily have done the work cheaper; — under $0.01 of main spend≈ always
Workflow cold starts workflow_cold_start_pct%Cache-write $ of the first call of the run's cold-started agents ÷ the run's priced costD2a D9A cost of the design, not waste≈ always
Agent tokens agent_tokenstokensDeduplicated usage of the agent's own transcript: the last line of each message.id (subagent transcripts stream output_tokens), without a fork's replayed first message (the parent's launching response, billed in the parent)D2a—never
MCP memory mcp_rssbytesRSS of the MCP server processD5—never
MCP calls mcp_callscountCalls of tools named mcp__<server>__*D2—never
Workflow runs agent_workflowscountsubagents/workflows/<run>/journal.jsonl: agents launched, finished (result) and failed per run; the run's agents are scanned like the top-level onesD2a—never
Spawn depth agent_depthcountDeepest spawnDepth among the agents (Claude Code caps it at 3)D2a—never
Teammates teammateslistMembers of ~/.claude/teams/<team>/config.json when this session leads the teamD12—never
Teammate cost teammate_costUSDThe teammate's own cost-state.totalCostUSD plus its priced calls after that line; all of them priced while it has none; — with no transcript when its file is not foundD2c D11 D9Claude Code's own number once the teammate ended≈ while it runs or after its ledger's moment
Teammate context teammate_contexttokensThe teammate's current context: the input of its last API call, as Panel 1 computes the lead'sD2c—never
Teammate tokens teammate_tokenstokensDeduplicated usage of the teammate's transcript (one figure per message.id), as the lead's Panel 2D2c—never
Teammate turns teammate_turnscountTurns of the teammate's transcript, human / machine: a <teammate-message> starts a machine turnD2c—never
Teammate state teammate_stateenumactive (isActive: true in the team config) · recent (no config; a line in the last 5 min) · ended (its file ends with a cost-state, or isActive: false) · gone (no ledger, no config, no recent line) · missing (known from the config or a spawn, no transcript found)D12 D2cNo process mapping: liveness is Claude Code's flag or line recencynever
Teammate waste teammate_wasteUSDThe cost of the teammate's current turn under one reason: idle (alive, no API call for 5 min, no tool call unanswered in its transcript) or errored (its last response was an API-error line and nothing followed for 60 s)D2c D9Structural evidence of its own transcript only; no inbox, no message body≈ always
Team waste team_wasteUSDΣ teammate_waste over the teamD2c D9—≈ always
MCP needs auth mcp_authlistdeferred_tools_delta.needsAuthMcpServers / failedMcpServers from the transcriptD2—never

Files

MetricUnitHow it is computedSourcesCaveatsEstimate
Touches file_touchescountRead / Edit / Write / MultiEdit / NotebookEdit calls per file path, plus Bash cat / sed -n / head / tail reads of itD8A read counts when its result arrivesnever
Lines ± file_lineslinesgit diff --numstat against HEAD at attach timeD7Outside a git repo the column is emptynever
Uncommitted uncommittedlinesgit diff --numstat HEAD (added, removed, files) and the last commit Claude Code summarised (gitOperation.commit) with the edits since itD7 D2—never
Rewind points rewind_pointscountfile-history-snapshot lines in the current turn (checkpoints /rewind can restore) and the Bash writes of the turn no checkpoint covers; per file: the checkpoint version (file-history-delta, ⚠ at v8+), IDE edits (edited_text_file), stale markers (staleRecovered, staleReadFileStateHint), edit → re-read → edit churnD2—never
Re-reads file_rereadscountWhole-file reads (Read or a Bash reader) with no Edit/Write in between; ⚠ at ≥ 3D8Ranged reads (offset/limit) and file_unchanged results do not count; the counter resets when the file changed under the model (an IDE edit, a stale-read recovery) and at every context boundarynever

Advisor

MetricUnitHow it is computedSourcesCaveatsEstimate
Estimated saving advice_savingtokenssecondsRule-specific estimate of what following the advice saves per remaining turnD2Ranking key; always an estimate

Coach

MetricUnitHow it is computedSourcesCaveatsEstimate
Context light coach_contextpercentThe context size as % of the exact window; ○ below 150k, ◐ from 150k (or ≥ 300k on a 1M window while a turn runs), ● inside the autocompact warn band (effective window − 13 000 − 20 000) or at ≥ 300k with a clean stop availableD1 D3Never a fixed 80 %: a deliberate 1M session sits amber≈ when the window is the model default
Cache light coach_cacheminutestokensMinutes of cache left (prompt_cache.expires_at, else last call + observed TTL ≈), or the re-write size when cold; ◐ inside the countdown band (the last 5 min of a 1 h entry, 2 min of a 5 m one), ● when a reply now would save ≥ 50kD1 D3—
Limits light coach_limitspercentThe 5 h window used; ◐ when the exhaustion fit lands before the reset or ≥ 80 %, ● on a rate-limit or spend-limit error line; — without the status lineD3 D1—never
Rework light coach_reworkstateOpen issues: consecutive failed calls of the turn (denials excluded), corrections (interrupts, rejected calls) in the last three turns, blocked calls; else the source edits since the last confirmed test run. The figure is a state word — N open, N unchecked, ok, or — before the session has called anything — never a 0 that means healthy and no data alike. ◐ after 10 min or 14 calls unverified, two fails, a PR without a review, an uncommitted tail; ● on a cascade, a denial streak, a correction streak, destructive git on a dirty tree, a commit without a checkD1 D7—never

Ask your session about it

cctop query … --json exposes every number, and the bundled cctop-insights skill teaches Claude Code to use it — ask "why is my cache hit ratio low?" in the session and get numbers plus one change to make. See plugin/skills/cctop-insights/SKILL.md.

Install & attach

brew install tomstagl/tap/cctop           # currently v0.9.1; or: cargo install cctop
claude plugin marketplace add tomstagl/cctop
claude plugin install cctop               # adds /cctop and cctop-insights
/cctop                                     # opens the dashboard: panel or terminal split

/cctop is the only command you need — see Two ways to see it for what decides panel vs. terminal, and docs/claude-code-panels.md for how the panel and the built-in /diff panel share one dock. If neither the panel nor a multiplexer split can attach, /cctop prints exactly what is missing and how to fix it — a rate-limit shim install, a terminal that isn't tmux/zellij/ WezTerm/Kitty/iTerm2, or cctop run --session <id> to run it by hand in a second terminal.

Repository

PathWhat
tasks/prd-cctop.mdProduct requirements, v1.1
ralph/prd.json43 dependency-ordered implementation stories
plugin/skills/Claude Code plugin skills
brand/Logo (SVG/PNG), build script, candidates

License

MIT. Brand fonts are IBM Plex under the SIL OFL.

More Mods

Mods

Secret Redactor

Automatically redacts API keys, credentials, IP addresses, and emails from terminal outputs and tool arguments before sending to the model.

securityprivacyredaction+3
by AI Dojo
GitHub
Claude Flightdeck
Mods

Claude Flightdeck

Live agent dashboard pane with context consumption, advisor timeline, permission verdicts, and subagent swimlanes.

agentdashboardvisualization+3
by Sam Casella
GitHub
Mods

Launch Codes

Interactive confirmation and one-time authorization codes for destructive or high-risk terminal commands.

safetyguardrailsconfirmation+3
by AI Dojo
GitHub

Command Palette

Search for a command to run...