Session Metrics
Runs scripts/session-metrics.py against the Claude Code JSONL log to produce
a timeline-ordered cost summary with per-turn and cumulative totals.
Dispatch — how to route this invocation
First positional argument received: $ARGUMENTS[0]
Full argument string: $ARGUMENTS
Read $ARGUMENTS[0] above and match it by literal equality against the
table below. Claude Code already tokenized the arguments shell-style, so no
parsing is required — just compare strings.
$ARGUMENTS[0] |
Route | Then read |
|---|---|---|
all-projects |
Instance-wide dashboard aggregating every project under ~/.claude/projects |
## Instance dashboard (all projects) below |
compare |
Two-session compare on JSONLs that already exist | ## Model comparison below, then references/model-compare.md before running |
compare-run |
Fully automated capture: spawns two claude -p sessions, feeds the suite, then runs --compare |
## Model comparison below, then references/model-compare.md "Workflow A — automated" |
compare-prep |
Print manual capture protocol + 10-prompt suite (fallback when headless is unavailable) | ## Model comparison below |
count-tokens |
API-key-only tokenizer check | ## Model comparison below |
export |
Natural-language export shortcut — scan full arg string to determine session vs project scope | ## Export shortcuts below |
project |
All sessions for the current project — timeline + per-session subtotals + grand total | ## Quick usage below (--project-cost); also scan remaining args for --output format flags |
project-cost |
Alias for project |
## Quick usage below (--project-cost); also scan remaining args for --output format flags |
| (empty, or any other value) | Default single-session report | ## Quick usage below |
This is the single gate that keeps compare mode off the natural-language
path. Do not infer the route from the user's chat history; only use the
literal value of $ARGUMENTS[0] above.
When the skill auto-triggers from a natural-language question ("how much did
this session cost?", "show me token usage"), there are no positional
arguments — $ARGUMENTS[0] is empty — and you always route to the default.
Phrases like "compare 4.6 vs 4.7 cost" arriving as natural language do NOT
produce $ARGUMENTS[0] = compare and must not route into compare mode;
answer them by running the default report on the current session and
suggesting /session-metrics compare-prep if the user wants a real
benchmark.
Pre-flight context
- skill-dir: ${CLAUDE_SKILL_DIR}
- session-id: ${CLAUDE_SESSION_ID}
Export shortcuts
Reached when $ARGUMENTS[0] is export. Scan the full argument string (not just $ARGUMENTS[0]) to determine scope and formats. Apply these checks in order (first match wins):
- Full arg string contains
all-projects→--all-projects --output <formats> - Full arg string contains
project(and not already caught above) →--project-cost --output <formats> - Otherwise → current session
--session ${CLAUDE_SESSION_ID} --output <formats>
Infer format flags from the argument string: html → html, csv → csv, md or markdown → md. Always add json alongside any requested format per the post-export audit convention (see ## Optional post-export audit below).
Always add --quiet to session and project export commands. When exporting, the per-turn detail lives in the written HTML/JSON, so the full stdout timeline is redundant — and at project scope (or for a long session) it can run to thousands of lines, spilling the run into a harness overflow file that buries the [export] path lines you need. --quiet collapses stdout to the legend + grand total + footer (plus the [export] lines), keeping the run inline. Do not add --quiet for --all-projects — its instance dashboard text is already compact and the flag has no effect there.
Examples:
| Full argument string | Command |
|---|---|
export session |
--session ${CLAUDE_SESSION_ID} --quiet --output json |
export session to html |
--session ${CLAUDE_SESSION_ID} --quiet --output html json |
export session metrics to html |
--session ${CLAUDE_SESSION_ID} --quiet --output html json |
export to html |
--session ${CLAUDE_SESSION_ID} --quiet --output html json |
export project |
--project-cost --quiet --output json |
export project to html |
--project-cost --quiet --output html json |
export project sessions |
--project-cost --quiet --output json |
export project sessions to html |
--project-cost --quiet --output html json |
export entire project's session metrics to html |
--project-cost --quiet --output html json |
export project metrics to html csv |
--project-cost --quiet --output html csv json |
export all-projects |
--all-projects --output json |
export all-projects to html |
--all-projects --output html json |
project and project-cost as the first arg also pick up --output flags from remaining args the same way (e.g. /session-metrics project metrics export to html → --project-cost --quiet --output html json).
Quick usage
# Current session (pinned to session ID — no heuristic)
uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py --session ${CLAUDE_SESSION_ID}
# Specific session ID
uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py --session <uuid>
# Specific project slug (use = when slug starts with "-")
uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py --slug=-home-user-projects-myapp
# Or via env var (always safe):
CLAUDE_PROJECT_SLUG="-home-user-projects-myapp" uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py
# List available sessions for this project
uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py --list
# All sessions — timeline + per-session subtotals + grand project total
uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py --project-cost
# Export to exports/session-metrics/ (one or more formats).
# Add --quiet on exports so a long timeline doesn't bury the [export] paths.
uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py --quiet --output json
uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py --quiet --output json csv md html
uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py --project-cost --quiet --output html
${CLAUDE_SKILL_DIR}is expanded by Claude Code to the skill's install directory (plugin cache, project-local copy, or bundled template — whichever applies). When running the script manually from a shell, substitute the actual path.
Quick shell wrapper. For manual runs outside Claude Code, the bundled
scripts/session-metrics-quick.shauto-locatessession-metrics.py(including the version-pinned plugin-cache install), detects the current project + newest session, and runs an HTML+JSON export. Pass--session <uuid>(or-s) to target a specific session instead — it resolves across all projects, so you can run it from a fresh low-context session to export an earlier heavy one; the HTML+JSON default still applies unless you pass--output. Other flags (--project-cost,--list, an explicit--output …) pass through verbatim.
Export formats
--output accepts one or more of: json csv md html
Text is always printed to stdout. Exports go to exports/session-metrics/ in the
project root, named session_<id8>_<YYYYMMDD>T<HHMMSS>Z.<ext> (single) or
project_<YYYYMMDD>T<HHMMSS>Z.<ext> (project mode). An index.html
manifest at the export root is refreshed after every export — open it to
browse all runs (newest first, with a latest-run-per-scope strip).
| Format | Contents |
|---|---|
json |
Full structured report with all turns, subtotals, model rates |
csv |
One row per turn: session_id, index, timestamp, model, tokens, cost |
md |
Summary table + per-session Markdown tables |
html |
Dark-theme report with summary cards + insights + chart. 2-page split by default (<stem>_dashboard.html + <stem>_detail.html); pass --single-page for one file. |
HTML-specific flags
| Flag | Purpose |
|---|---|
--single-page |
Emit one self-contained HTML instead of the dashboard+detail split. |
--chart-lib {highcharts,uplot,chartjs,none} |
Choose the chart renderer. Default highcharts (richest visualization, vendored, SHA-256-verified, non-commercial license). uplot and chartjs are MIT-licensed alternatives. none emits a detail page with no JS dependency. See scripts/vendor/charts/README.md for per-library license terms. |
--peak-hours H-H |
Translucent band on the hour-of-day chart (e.g. 5-11). Community-reported, not an Anthropic SLA. |
--peak-tz <IANA> |
Timezone the peak hours are defined in (default America/Los_Angeles). |
Other useful flags
| Flag | Purpose |
|---|---|
--tz <IANA> |
IANA timezone for time-of-day bucketing and timeline/export timestamps. Defaults to the system local tz (auto-detected via TZ env var or the OS setting). |
--utc-offset <H> |
Fixed UTC offset, DST-naive. Use --tz for DST-aware. |
--no-cache |
Skip ~/.cache/session-metrics/parse/ and always re-parse from scratch. |
--prune-exports <N> |
Prune the export directory: keep the newest N runs per retention group (each session id, the project series, each compare pair, the instance dated dirs) and delete older runs' files. audit_* sidecars and unrecognised files are never touched. Dry run by default — add --yes to actually delete. |
--quiet / -q |
Suppress the per-turn timeline on stdout — print only the legend, scope header, grand-total subtotal, and footer (the [export] path lines still print). Keeps stdout small on large session/project exports so the export paths aren't buried under an overflow-sized dump; the full per-turn detail still lands in the written HTML/JSON. Session and project scopes only (no effect on --all-projects). |
--no-self-cost |
Suppress the self-cost meta-metric (stderr [self-cost] line, HTML KPI card, and JSON self_cost key). |
--redact-user-prompts |
Replace freeform prompt_text / prompt_snippet / assistant_text / assistant_snippet with [redacted] on every turn of single-session and project JSON exports, plus compare HTML. Tool inputs, slash-command names, and structured cost / token fields stay visible. HTML / MD / CSV / text are NOT redacted. |
--export-share-safe |
One-flag pre-share gesture (v1.36.0+): implies --redact-user-prompts and --no-self-cost, and chmods every written export file to 0600 (rw-------). For full prompt redaction, pair with --output json. |
--no-include-subagents |
Skip spawned subagent JSONL files. Subagents are included by default; use this for faster runs when subagent detail is not needed. |
--cache-break-threshold <N> |
Turns whose input + cache_creation exceed N are flagged as cache-break events (default 100 000). Matches Anthropic's session-report convention. |
--no-subagent-attribution |
Disable Phase-B subagent → parent-prompt token attribution. Default behaviour rolls every subagent's tokens up onto the user prompt that spawned the chain (additional attributed_subagent_* fields, no double-counting). |
--sort-prompts-by {total,self} |
How to rank top prompts in HTML/MD output. total (default) = parent + attributed subagent cost, surfaces cheap-prompt-spawning-expensive-subagent turns. self = parent only (pre-Phase-B order). CSV/JSON keep self ordering for stability regardless of this flag. |
Invocation note for the AI. Don't pass
--tzor--utc-offsetunless the user explicitly asks for a specific timezone. The script auto-detects the user's system tz and renders all human-facing timestamps (timeline, session headers, generated-at banner, block anchors) in that tz. JSON/CSV rawtimestampfields stay UTC ISO-8601 as a machine-readable audit trail. Don't pass--include-subagents— subagents are included by default. Only pass--no-include-subagentsif the user explicitly asks for a faster/leaner run without subagent detail.
Output columns
| Column | Meaning |
|---|---|
# |
Deduplicated turn index |
Time |
Timestamp of the turn in the user's local timezone (auto-detected; override with --tz / --utc-offset). Header shows the active tz label. Raw timestamp fields in JSON/CSV exports remain UTC ISO-8601 (...Z) for machine-readability. |
Input |
Net new input tokens (uncached portion only — cache reads/writes are shown separately) |
Output |
Output tokens generated (includes thinking + tool_use block tokens) |
CacheRd |
Tokens served from prompt cache (cheap) |
CacheWr |
Tokens written to prompt cache (one-time). 1h / mix badge marks turns that used the 1-hour TTL tier; CSV/JSON expose cache_write_5m_tokens and cache_write_1h_tokens as dedicated columns. |
Content |
Per-turn content-block distribution. Letter encoding T thinking, u tool_use, x text, r tool_result, i image (zero counts omitted). Renders only when at least one turn carries any content block. CSV/JSON expose thinking_blocks / tool_use_blocks / text_blocks / tool_result_blocks / image_blocks as dedicated per-turn columns. |
Total |
Sum of the four billable token buckets |
Cost $ |
Estimated USD for this turn |
Deep-dive on exact column semantics, JSON keys, and detection rules:
references/jsonl-schema.md.
Per-turn drill-down (HTML only)
In the Detail page and single-page variants, every Timeline row is clickable (keyboard: Enter / Space) and opens a right-side drawer showing the user's actual prompt, any slash command, the tools that were called (with one-line input previews), the content-block mix, the per-turn cost/token breakdown, and the assistant's reply. Prompt and assistant text are truncated to ~240 characters with a Show full prompt / Show full response toggle that reveals the full text (up to a 2 KB cap on assistant text). Close with the × button, Esc, or a backdrop click — focus returns to the originating row. Below the Timeline, a collapsible Prompts section lists the top-20 most-expensive prompts; each row opens the same drawer. The Dashboard variant has no Timeline, so it's unaffected.
Footer shows session totals + cache savings vs a hypothetical no-cache
run. Conditional dashboard cards appear when their feature was used in the
session: Cache TTL mix (when any 1h-tier cache writes happened),
Extended thinking engagement (when any turn carried a thinking block),
Tool calls (top-3 tool names), Session resumes (timeline divider at
each claude -c resume point, detected from /exit + synthetic-turn
fingerprint — lower-bound count), and the Usage Insights panel
(prose-style pattern characterisations inspired by Anthropic's /usage
command, auto-hide below threshold, exposed in JSON under usage_insights
and in Markdown under ## Usage Insights).
Cross-cutting sections (v1.6.0 — inspired by Anthropic's session-report)
Three additional sections auto-hide when empty and render at every scope
(single session / --project-cost / --all-projects). All three feed the
same by_skill, by_subagent_type, and cache_breaks keys in the JSON
export.
- Cache breaks — single turns whose
input + cache_creationexceeds the threshold (configurable via--cache-break-threshold, default 100 000). Each row names which turn lost the cache; the HTML version is expandable and shows ±2 user-prompt context around the event. Complements the overall cache-hit % with actionable "here is where it blew up" detail. - Skills & slash commands — one row per named skill or
/slashcommand, columns: invocations, turns attributed, input / output / cache tokens, % cached, cost, % of total. Attribution model: a slash-prefixed user prompt sets the "current skill" for that prompt and its follow-up turns; a Skill-tool invocation overrides attribution for its own turn. Slash-prefixed keys (e.g./session-metrics) are de-slashed so they merge with Skill-tool invocations of the same name. - Subagent types — one row per resolved
subagent_type(fromAgent/Tasktool_useinput.subagent_type). Shows spawn count always; token/cost columns populate from subagent JSONLs (default behaviour — pass--no-include-subagentsto skip). A Model column (v1.87.0) names the model that actually served the type's turns, read from those transcripts;+Nmarks a type served by more than one model, with the full per-model turn split on hover (HTML), inline (Markdown), asmodel:count|model:count(CSV), and as amodelsmap (JSON). This is the column that makes a pin override visible: anAgentcall passingmodeloverrides the agent file's frontmattermodel:, and a generic alias (opus) resolves to the current family member — so a model-pinned agent can legitimately run on a different model than its pin without the type-level turn count changing.
UUID-based dedup runs at project and instance scope to prevent
resumed-session replays from double-counting. Session scope keeps the
existing message.id streaming-split dedup.
Subagent → parent-prompt attribution (v1.7.0 — Phase B)
Subagent token usage is included by default and rolls up onto the user prompt that originally triggered the chain:
- Three new turn-record fields populate on the spawning prompt's row —
attributed_subagent_tokens,attributed_subagent_cost,attributed_subagent_count. They are purely additive:cost_usd/total_tokenson every turn (parent and subagent) are unchanged, so existing aggregators and the session total are untouched. Display layers read both columns separately. - Nested chains (subagent A → subagent B) attribute B's tokens onto
the root user prompt, not onto A. Implemented via an iterative
resolve over
(tool_use.id, agentId)linkage extracted from the parent'stoolUseResult.agentIdfields, with a cycle guard. - HTML prompts table sorts by
cost_usd + attributed_subagent_costby default (configurable via--sort-prompts-by). A new "Subagents +$" column is auto-shown when at least one top-20 row has attributed cost. The prompt snippet gains a "+N subagents" badge. - CSV per-turn export always includes the three attribution columns (zero on rows without spawn activity) — column count stays stable.
- JSON report exposes top-level
subagent_attribution_summarywithattributed_turns,orphan_subagent_turns,nested_levels_seen,cycles_detected. Useful for sanity checks when pointing at unfamiliar history.
Disable subagent loading entirely with --no-include-subagents (fastest, no subagent token detail).
Disable only the attribution rollup while still loading subagents with --no-subagent-attribution (compare against pre-Phase-B reports).
Instance dashboard (all projects)
Reached when $ARGUMENTS[0] is all-projects, or when the user asks for the
total cost across every project ("how much have I spent on Claude Code
overall?", "what's my total spend across all projects?", "which project is
costing me the most?").
uv run python ${CLAUDE_SKILL_DIR}/scripts/session-metrics.py --all-projects --output html md csv json
Before running an all-projects report, read
references/instance-dashboard.md. It
covers the dated output bundle + per-project drilldowns, --no-project-drilldown
(fast/CI path), and the multi-instance directory overrides
--projects-dir / --cache-dir / --export-dir (the last three apply at
every scope — env-var equivalents CLAUDE_PROJECTS_DIR /
CLAUDE_SESSION_METRICS_CACHE_DIR / CLAUDE_SESSION_METRICS_EXPORT_DIR). This
detail is deferred so single-session reports don't pay for context they rarely
use.
Model comparison
Reached only when $ARGUMENTS[0] is compare, compare-run,
compare-prep, or count-tokens (see the Dispatch section at the top
of this file).
Default pair is claude-opus-4-6[1m] vs claude-opus-4-7[1m] —
the 1M-context tier, because that matches Claude Code's shipping Opus
routing. Users opt into the 200k-context variants by passing the
unsuffixed IDs explicitly. Mixed-tier pairs are accepted and surface
the existing context-tier-mismatch advisory on the report.
| Mode | Use when |
|---|---|
compare-run (preferred) |
User wants the report with no manual capture. Orchestrator spawns two claude -p sessions via subscription auth, feeds the canonical suite, then runs --compare. Zero API key. |
compare |
Two session JSONLs already exist (either from a prior compare-run, or from manual /model + paste). Input is a path / UUID / last-<family> / all-<family> token. |
compare-prep |
Print the manual capture protocol. Only suggest when claude -p is unavailable (e.g. CI container without the CLI). |
count-tokens |
API-key tokenizer smoke test. NOT a subscription path; do not suggest to users comparing two subscription sessions. |
Output formats for compare mode. Compare mode supports --output text md json csv html. The HTML report is always single-page (a compact scored per-prompt table); --single-page and --chart-lib are ignored when --compare is active.
compare-run auto-extras. When compare-run is invoked with
--output <fmt>, it emits the compare report and five companion
files in exports/session-metrics/: a per-session dashboard + detail
(HTML) and raw JSON for each side, plus a Markdown analysis scaffold
(compare_<a8>_vs_<b8>_<ts>_analysis.md) with headline ratios,
per-prompt table, cost decomposition, and a bolded decision-framework
verdict. Prose sections carry {{TODO}} placeholders so a follow-up
chat can fill them in. Pass --no-compare-run-extras to skip the
companions and emit only the compare report. Without --output,
nothing writes to disk (text-only stdout path preserved).
Custom prompts. Users can add their own prompts to the comparison suite with
--compare-add-prompt "text" — no file format knowledge required. The prompt is
saved to ~/.session-metrics/prompts/ and runs automatically on every subsequent
--compare-run. Use --compare-list-prompts to preview the active suite and call
count before spending on inference. Use --compare-remove-prompt <name> to remove.
Full guide: references/custom-prompts.md.
Prompt steering. --compare-run-prompt-steering <variant> wraps every
prompt in the suite with a steering instruction before feeding it to
claude -p. The four built-in variants are concise,
think-step-by-step, ultrathink, and no-tools. The wrapper is
applied symmetrically to both sides so the A/B stays clean — what shifts
is the model's behaviour under the same instruction, surfaced as token /
cost / thinking / tool-call deltas. Use
--compare-run-prompt-steering-position {prefix,append,both} (default
prefix) to control where the steering text lands relative to the body.
IFEval pass rates may differ from the unsteered baseline by design —
predicate breakage (e.g. "be concise" violating the 50-word stacked
constraint) is the measurement, not a regression. For multi-variant
sweeps with auto-rendered comparison articles + cross-variant summary,
use the benchmark-effort-prompt skill instead.
Before proposing any compare-mode command, read
references/model-compare.md. That
doc has the full flag table, four workflow recipes, 4-way Opus combo
matrix, IFEval predicates, advisory semantics, and troubleshooting.
The eager content in this file deliberately stays minimal so
single-session reports don't pay for compare-mode context they don't
use.
Optional post-export audit
When any --output format is specified (html, csv, md, or json), always
add json to the invocation if absent (e.g. --output html → --output html json) so the JSON export — and the audit suggestion below — can always be
produced.
After the [export] lines (and the optional [self-cost] line) print, read
references/post-export-audit.md and append
the scope-appropriate /audit-session-metrics suggestion it specifies (session
/ project / instance, determined from the [export] JSON filename). Do not
invoke audit-session-metrics yourself — it is a separate, user-initiated
audit. The reference is deferred so non-export reports don't carry it.
Automatic Tasks companion (no extra command)
For a single-session HTML export whose JSON request_units array has between
2 and 40 entries, auto-generate a Tasks companion in the same turn (the user
gets it for free). Read
references/tasks-companion.md for the full
procedure before doing so. In short: add --task-companion-nav to the export
invocation (alongside the always-on json), then run --prepare-tasks, edit
the seeded <stem>_grouping.json (real titles, merges/splits, one-line
rationales, verdicts), and run --render-tasks. You are an editor, not an
author — the script writes a renderable skeleton; you only refine the semantic
parts.
Scope gate — single-session only. Never auto-generate for --project-cost
or --all-projects (and don't add --task-companion-nav there): semantic task
grouping does not span sessions. At project/instance scope, skip the companion
and the nav button and tell the user /task-breakdown can be run on a single
session if they want one. Skip too when there is only 1 request unit, or
more than ~40 (too large for a clean auto-grouping — say so and point to
/task-breakdown). The standalone /task-breakdown <json> skill remains for
re-grouping a saved export later.
Auto-insights companion (v1.78.0+)
A prose "Insights" companion page (*_insights.html + *_insights.md) that you
write over a deterministic digest. Python owns every number; you write only
prose — same contract as the Tasks companion. Two lenses: summary (what got
done & why) and effectiveness (waste & how to improve). Offer this when the
user asks "what did I get done this session", "where did my spend go", "how
could I have been more effective", or similar — or after a JSON export when a
prose write-up would help.
Three-step flow (mirrors prepare/render-tasks):
--prepare-insights <export.json>— prints a bounded, truncated digest (totals, session health/behaviour, velocity, top cost drivers, per-request one-liners with no-prompt/agent-continuation noise excluded) to stdout and writes a candidate<stem>_insights.jsonskeleton. Shape it with--insights-lens {summary,effectiveness}(defaultsummary) and optional--insights-focus "<question>"(free-text steering, e.g."why was this session so expensive?").- Read the digest from stdout and fill the skeleton's
headline+ sectionbodyfields (andrecommendationsfor the effectiveness lens) with prose. Quote figures from the digest verbatim — never recompute a number. You are an editor, not an author: the skeleton already renders. --render-insights <export.json> <insights.json>— validates the prose and pairs it with FACTS recomputed from the export (the prose is never trusted for math), then writes<stem>_insights.html+<stem>_insights.mdnext to the export. Report the paths and offer to open the HTML.
The digest excludes no-prompt and ↳ agent-continuation units so the prose
reflects real interactive work. A zero-edit skeleton still renders a correct
page (facts strip + a "prose not yet written" note) — graceful degradation.
Self-cost meta-metric
session-metrics tracks its own running cost in the current session.
After the [export] lines a [self-cost] stderr summary prints the
prior turns' tokens / dollars (the current run is not yet written
to the JSONL when the script reads it). The HTML dashboard surfaces
the same number as a "Skill self-cost" KPI card. The JSON export
carries it as the top-level self_cost key.
Pass --no-self-cost to suppress all three surfaces — useful for
clean test snapshots or for users who find the meta-metric noisy.
Reference files
references/pricing.md— Per-model token prices used for cost calculation. Read when the user asks about pricing or you need to add a new model.references/jsonl-schema.md— JSONL entry structure + full output-column semantics, cache-TTL split rationale, content- block distribution, resume detection. Read when debugging missing data, extending the script, or interpreting any non-obvious column/key.references/model-compare.md—--compareworkflow, prompt-suite catalogue, IFEval predicates, interpretation guide. Read when$ARGUMENTS[0]routes into compare mode.references/custom-prompts.md— Step-by-step guide for adding, removing, and previewing custom prompts for--compare-run. Read when the user asks how to add their own prompts or customise the suite.references/platform-notes.md— Windowstzdatacaveat for IANA--tznames, the--strict-tzescape hatch, and timezone-contract summary. Read when the user reports a timezone warning, asks about Windows support, or wants CI-safe tz handling.
How session detection works
- Derives the project slug from
cwd: replaces/→-, strips leading-. - Scans
~/.claude/projects/<slug>/for*.jsonlfiles (excludessubagents/). - Picks the most recently modified file as the current session.
- Override with
--session <uuid>or--slug <slug>when needed.
Deduplication
Each API response is written to the JSONL multiple times (streaming, tool
completion, final). The script deduplicates on message.id — keeping only the
last occurrence so token counts reflect the final settled value.