Analyze Claude Code transcripts for cost, behavior, and review-activity statistics; the `cost` subcommand covers token cost, cache efficiency, and branch- or repo-scoped dollar cost. TRIGGER when: needing any statistic derived from Claude Code transcripts — cost, cache efficiency, model mix, subagent-vs-main split, test-failure convergence, session or branch history, a denial/friction census, or a per-session record of typed prompts and corrections. DO NOT TRIGGER when: the ask is a case study or annotated timeline built on top of these metrics (use `transcript-narrative`), or the number was already produced by this toolkit earlier in the session.
Install
npx skillscat add jcdendrite/claude-config/transcript-analysis Install via the SkillsCat registry.
The toolkit lives at ~/.claude/scripts/transcript-analysis.py. Run it directly from the shell.
Scope confirmation
Before quoting a corpus-wide statistic from this toolkit's output, include the resolved-scope header line verbatim in what you report, and if that line reads "1 root (no ~/.claude/transcript-config-dirs declared)", ask the user whether other Claude accounts exist before treating the number as complete.
"I found nothing" is one of those statistics. A zero-match run is the case most likely to be a scoping failure rather than a real result, so quote its header too before reporting the absence — never report an empty result without stating the corpus it was empty across.
cost --summary prints no resolved-scope header; it states its scope on its own Scope: line instead, in the form Scope: this repository only, <branches>. This account only, <window>. It is always scoped to this repository, the given branch filter (or all branches), and the active account only, so quote that line rather than asking about other repos, branches, or accounts.
Which subcommand to use
| Question | Subcommand |
|---|---|
| Which branches exist? What models were used on each? | buckets |
| Did a branch converge or thrash during test debugging? | fail-seq --branches <branch> |
| Did the user express frustration more with one model? | struggle --branches <branch> |
| How much logged time was active vs idle gaps? | duration --branches <branch> |
| How much work went through subagents vs the main thread? | subagents --branches <branch> |
| Map branches to PRs; count per-author review comments | pr-link --branches <branch> |
| Which sessions ran review skills, hit a hook denial, or spawned reviewer agents? | review-trace |
| Which denial/friction shapes recur across sessions — a corpus-wide census, not per-session? | review-trace --deny-summary |
Which skills did a branch invoke, by source (auto-trigger / routing / /slash)? |
skill-invocation --branches <branch> |
| Where did a human push back on an AI review's output? | judgment-pair |
| What prompts did I write, and where did I redirect Claude? | user-input |
| Is Opus spend doing Sonnet-tier code-read/write in parent sessions? | audit-routing --since 35d --redact |
| Which lever costs the most in actual dollars — cache read/write, output, or input? | cost --since 30d |
| How much does idle-gap prompt-cache TTL-expiry rebuild cost, and is it concurrent-session switching or real breaks? | cache-rebuild --since 30d |
| Which prompt-cache TTL should the main or everything-else bucket run, and does every root agree? | cache-rebuild --ttl-verdict --since 30d |
| What did this branch cost, as a publish-ready aggregate for a PR body? | cost --this-repo --branches <branch> --summary |
| Are reviewer dispatches producing real findings, and do sessions then edit what was cited? | reviewer-yield --since 30d --redact |
| Is spend climbing week over week? | cost-trend |
| What's this repo's durable week-over-week cost/efficiency history, beyond what live transcripts still retain? | cost-ledger |
| Which client or profile does spend belong to? | cost --by-project --config-dir <dir> |
| What fraction of Opus code-read turns are clearly dispatchable vs. read-then-edit loops? | audit-routing-shape --since 35d |
| Pull a random sample of Opus code-read turns for delegation judgment curation | audit-routing-samples --since 35d --sample 50 --seed 1 |
| What did a specific subagent type actually cost over a date range, and what would it have cost at another model? | subagent-mix --since-date <date> --until-date <date> --reprice-as <model-id> |
Reading fail-seq output
Total runs: 12 Failing: 3 (25.0%) Longest consecutive-failing streak: 2
Sequence: 0 0 5 0 0 0 3 0 0 0 0 0- Convergent (expected): a spike followed by zeros — the signature of a root-cause fix. The sequence above is convergent.
- Thrashing (flag for review): oscillation like
8 6 9 7 8with no sustained run of zeros — a model not closing on a fix. - The
longest consecutive-failing streakis the load-bearing metric. A streak of 1–2 is normal (fix lands next run). A streak of 5+ warrants a closer look at the sequence and what was happening between those runs.
Reading user-input output
Total sessions: 12 Fresh prompts: 47 Explicit corrections: 6 (12.8%)Three classifications appear in the output:
- INITIAL — the first typed prompt in a session. Sets the direction of the conversation.
- FOLLOWUP — a subsequent prompt with no frustration-phrase match. Quiet redirects: refining scope, asking follow-up questions, or continuing the thread without explicit frustration.
- EXPLICIT_CORRECTION — a subsequent prompt containing a phrase from the
strugglephrase list (e.g., "try again", "not that"). The matched phrase is noted in parentheses.
Every non-INITIAL prompt counts as a course correction under the "frustration phrases + all follow-up prompts" definition. The FOLLOWUP vs EXPLICIT_CORRECTION split lets you distinguish polite redirects from explicitly frustrated ones.
Use --corrections-only to strip initial prompts when you only want the steering moments. Use --since/--until to focus on a date window (e.g., a sprint or a specific project phase).
Caveats
- Every subcommand's default scope unions all config dirs in
~/.claude/transcript-config-dirs, exceptcost --summary(active config dir only) — seedocs/transcript-analysis.md's "Corpus scope: the declared-roots file" section. - The resolved-scope header always prints, even at one root or zero matches, except
cost --summary(no header) — see "Scope confirmation" above. - Redaction covers only
cost,cost-trend,context-distribution,audit-routing, andcache-rebuild; every other subcommand prints raw project labels, branch names, or paths. --branchesfilters records bygitBranchstring only, never by project dir or root — under a multi-root scope it pools same-named branches across every declared account into one tally, with no per-account signal.- The
N failedcount is a coarse proxy: it matches anyN failedin tool output, including pre-existing failures and intentional baseline runs. Treat the sequence view as the primary read; the aggregate rate is corroborating. - Subagent (
isSidechain) turns are excluded fromfail-seqandstruggle— reviewer,Explore, andcode-writeragents are not the debugging surface these subcommands measure. - Durations from
durationare wall-clock dominated by idle gaps. Look atActive(min), notSpan(min). pr-linkrequiresghand network access.cost-ledger --recordwrites to$CLAUDE_CONFIG_DIR/cost-ledger.mdby default (override withCOST_LEDGER_PATH) and requires~/.claude/scripts/config-get.sh cost_ledger_recordingto exit 0 (seedocs/config-file.md); every other subcommand, andcost-ledger's own default read mode, is local-only and makes no writes.- A model-vs-model comparison is only meaningful when there are multiple all-Opus and all-Sonnet execution branches. One or two branches per model is directional, not a controlled A/B.
review-tracelocates candidate sessions; it does not judge whether a review caught a material issue — that read is qualitative. Use--since/--until(inclusive day bounds) for before/after-a-date analysis,--deny-onlyto isolate sessions that hit an enforcement hook, and--deny-summaryfor a corpus-wide census by hook, by command shape, and their cross-tab, plus a friction-kind breakout — a distinct axis from denials — seedocs/transcript-analysis.md'sreview-tracesection for the full output shape.judgment-paircaptures what the human said immediately after a review output. Tool-result turns,isMetainjections, andisCompactSummaryrecords between the review and the user reply are automatically skipped. Use--outto save output to a file for offline curation.--corrections-onlyandstrugglefalse-flag forwarded<task-notification>subagent-summary text as human corrections (see GH-752) — verify a flagged turn is inside an actual human message before treating it as one.user-inputprints raw prompt text verbatim regardless of--redact— that flag anonymizes project labels and session IDs only, matching every other--redactimplementation in this file (none scrub message content). Review output before pasting it anywhere public.audit-routing --redactremaps project names to anonymized labels for public reporting — use this flag when posting output to GitHub issues.costredacts project names and session IDs by default (the opposite ofaudit-routing's opt-in--redact) — pass--no-redactonly for local use, never for output headed to a public issue.cost's default redaction makes labels safe, not figures. Under a multi-root scope its totals, rates, and medians are pooled absolutes across accounts, whichdocs/private-project-redaction.md§ "Publishing a tooling measurement" keeps out of every public artifact — report them to the owner instead.--summaryis the one mode scoped for publication;--share-onlykeeps raw absolutes out of the agent's context on a wider read that stays private.cost --config-dirunions extra account profiles into one report on top of the declared-roots default;--this-repoand--no-redactare refused in that mode. Redacted labels (private-project-N,account-N) are comparable between two reports only when the same declared-roots file produced both — a changed root set renumbers every ordinal (each run still prints a corpus fingerprint).--projectsdefaults to*— every project across every declared root; scope it with--this-repoor an explicit glob (seedocs/transcript-analysis.md's "Scoping to this repo" section for the derivation and its gaps).buckets' Date range column describes whatever the glob matched rather than a bounded window —bucketstakes no--since/--until; usereview-trace --since/--untilfor a bounded window.review-traceoutput is not publish-safe under the default multi-root scope — each event line's branch string can carry a ticket ID or project name. No flag currently guarantees single-account scope onbuckets,review-trace,fail-seq,struggle,duration,subagents, orpr-linkshort of an explicit single--config-dir—--this-repodoes not imply single-account scope, since it unions across every declared root by default. Name--config-diras the one narrowing control before quoting any of these seven anywhere public.
Writing up a finding for a durable or public document
- Resolving real identity to read a session's own trace content is a legitimate internal step, but never let it leave your own reasoning — the write-up states the generalized fact only. (
--branches/--this-repofiltering matches the real underlying value, so correlating a redacted, opaqueaccount-N/branch-Mfigure back to its actual transcript may require temporarily resolving the real branch or repo — for example, to judge a long round-tail's genuine-work-vs-thrash.) Never state any of:- the branch
- the repo
- the PR number
- the account name
- A generalized statistical fact about a single outlier is safe to publish only when stripped of anything that could correlate it back to its source. "One branch reached 50 review rounds" is fine on its own — nothing to redact. (Use a made-up round number for this kind of illustration, not a real observed figure — a real one drifts out of date the next time someone re-measures.) Any of the following turns the same fact into a structural fingerprint (see the repo CLAUDE.md's "Also redact structural fingerprints and provenance" section) even with the name stripped out:
- a date
- a dollar figure specific to that one item
- detail specific enough to narrow which branch or project it could be (e.g. naming the exact hook, gate, or denial message that fired)
- Don't enumerate account or project types in a write-up, even generically. A phrase like "across every account (type-A, type-B, type-C)" discloses the operator's own business structure — how many kinds of relationships exist — independent of any name. Prefer an aggregate phrasing ("across the corpus," "pooled across N accounts") that states the scope was multi-account without characterizing what the accounts are.
Example usage
# Survey all branches
python3 ~/.claude/scripts/transcript-analysis.py buckets
# Check if a branch's debugging loop converged
python3 ~/.claude/scripts/transcript-analysis.py fail-seq --branches feat-TICKET-101
# Compare two branches side by side
python3 ~/.claude/scripts/transcript-analysis.py fail-seq --branches feat-TICKET-101,feat-TICKET-202
# Link branches to PRs and count one author's review comments
python3 ~/.claude/scripts/transcript-analysis.py pr-link \
--branches feat-TICKET-101,feat-TICKET-202 --author alice
# Find sessions that hit an enforcement-hook denial
python3 ~/.claude/scripts/transcript-analysis.py review-trace --deny-only
# Review activity in a date window (e.g. before vs after a skill landed)
python3 ~/.claude/scripts/transcript-analysis.py review-trace --since 2026-01-01 --until 2026-03-31For narrative case studies and annotated timelines built on top of these metrics, use transcript-narrative.