/profile — Claude Code session profiler
The user is invoking the profiler. Read the user's argument list, then run exactly one Bash command that calls scripts/cc_profiler.py with the matching subcommand. Print the script's output verbatim to the user — do not paraphrase or summarize the table; users want to see real numbers.
Resolving the script path
Important — read this carefully, do not improvise.
${CLAUDE_PLUGIN_ROOT} is not reliably injected into the bash commands you emit from this skill. We learned this the hard way: writing ${CLAUDE_PLUGIN_ROOT:-…} collapses to the fallback because the variable is empty at bash-execution time, and the fallback then mis-resolves to a directory. Do not reference CLAUDE_PLUGIN_ROOT here.
Instead, locate the script by globbing the plugin cache (where Claude Code installs the plugin) and falling back to the dev repo. Use this exact 3-line block — copy it verbatim, do not rewrite:
SCRIPT="$(ls -1d ~/.claude/plugins/cache/*/claude-code-profiler/*/scripts/cc_profiler.py 2>/dev/null | sort -V | tail -1)"
[ -n "$SCRIPT" ] || SCRIPT="${CLAUDE_PROJECT_DIR:-$PWD}/scripts/cc_profiler.py"
[ -f "$SCRIPT" ] || { echo "cc_profiler.py not found at '$SCRIPT'. Install the plugin or run from the repo." >&2; exit 1; }
How it resolves:
- Plugin install (preferred) — glob picks the highest-versioned
cc_profiler.pyunder~/.claude/plugins/cache/<marketplace>/claude-code-profiler/<version>/scripts/.sort -V | tail -1ensures we always run the newest installed version, ignoring stale older versions kept around during the update grace window. - Dev mode fallback — if no plugin install is found, use
$CLAUDE_PROJECT_DIR/scripts/cc_profiler.py(or$PWDif neither var is set). This is for working directly inside a clone of the source repo. - Fail-fast — if neither path resolves to a real file, exit 1 with a clear message. Never fall through to
python3 "$CLAUDE_PROJECT_DIR" …or any other variable that holds a directory; that producescan't find '__main__' module in '…'.
Subcommands
Always emit one Bash invocation in this exact shape — the resolver block followed by the python call. Do not edit the resolver:
SCRIPT="$(ls -1d ~/.claude/plugins/cache/*/claude-code-profiler/*/scripts/cc_profiler.py 2>/dev/null | sort -V | tail -1)"
[ -n "$SCRIPT" ] || SCRIPT="${CLAUDE_PROJECT_DIR:-$PWD}/scripts/cc_profiler.py"
[ -f "$SCRIPT" ] || { echo "cc_profiler.py not found at '$SCRIPT'. Install the plugin or run from the repo." >&2; exit 1; }
python3 "$SCRIPT" <subcommand> <args...>
Map the user's arguments to <subcommand> <args...>. Pass arguments through verbatim — do not interpret tags or notes yourself.
| User said | <subcommand> <args...> |
|---|---|
/profile start [name] [--tag k=v]... [--note "..."] |
start [name] [--tag k=v]... [--note "..."] |
/profile status |
status |
/profile mark <label> |
mark <label> |
/profile stop [--format=...] [--export DIR] |
stop [--format=...] [--export DIR] |
/profile retro [--since WHEN] [--until WHEN] [--name NAME] [--format=...] [--export DIR] |
retro [--since WHEN] [--until WHEN] [--name NAME] [--format=...] [--export DIR] |
/profile reset |
reset |
/profile (no args) |
status (treat as status) |
retro is for the case where the user wants a profile but never called start. Pass --since/--until through verbatim. Recognized values for WHEN: ISO-8601 timestamp, epoch seconds, a relative duration (30m, 1h30m, 2d12h), or a literal — now, first (first transcript timestamp), last-prompt (most recent user prompt). Defaults: --since=first, --until=now. Retro never touches the active-profile pointer, so it's safe to run while a start is in flight.
If the subcommand is unrecognized, replace the last line with python3 "$SCRIPT" --help and show the usage.
What the profiler does
- start captures: session id, transcript path, cwd, git sha + dirty status, env (CLAUDE_*/ANTHROPIC_*/OTEL_*), model from latest assistant turn. Refuses if a window is already active.
- status shows elapsed time, marks, and a live count of turns since start.
- mark appends a
(timestamp, label)to the active window — useful for "docker pull starts here" or "benchmark begins" boundaries. - stop scans the transcript JSONL between start and now, computes all metrics, prints a table (or json/markdown), and writes artifacts under
~/.local/state/claude-code-profiler/windows/<window_id>/:profile.json— full structured reportprofile.md— readable markdownevents.jsonl— normalized event streamtranscript.snippet.jsonl— raw rows in window (for replay)state.json— frozen start-time metadata
- retro runs the same scan as
stopbut over an explicitly-specified[since, until]window of the existing transcript — no priorstartrequired. Writes the same artifact bundle into a newwindows/<id>/, withstate.jsoncarryingmode: "retroactive"so the bundle is self-describing. Does NOT touch the active-profile pointer. - reset discards the active window pointer without producing a report.
Notes for you (the assistant)
- Costs are estimated from a static price table (see top of
cc_profiler.py). Always pass that disclaimer through to the user. - Per-tool times are approximate in transcript-only mode (parallel tool bundles are split evenly across tools). The profile.json marks this with
tool_time_by_tool_approx: true. If a user asks for exact per-tool timing, mention they can wire hooks; the profiler ingestsevents.from_hooks.jsonlautomatically. - After running the script, do not add your own commentary unless the user asked a question — let the table speak.
- If the user invokes
/profile stopand the window has been short (<5s) or has zero turns, still print whatever the script outputs; that's a real signal the user can act on.
Optional: hook-augmented timing
For exact per-tool start/end times, the user can wire these hooks in .claude/settings.json (each pipes the Claude-emitted JSON payload to the profiler's _hook subcommand, which is inert when no window is active). Use ${CLAUDE_PLUGIN_ROOT} when this plugin is installed; swap to ${CLAUDE_PROJECT_DIR} if running from a clone:
{
"hooks": {
"PreToolUse": [{"hooks": [{"type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/cc_profiler.py _hook PreToolUse"}]}],
"PostToolUse": [{"hooks": [{"type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/cc_profiler.py _hook PostToolUse"}]}],
"UserPromptSubmit": [{"hooks": [{"type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/cc_profiler.py _hook UserPromptSubmit"}]}],
"Stop": [{"hooks": [{"type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/cc_profiler.py _hook Stop"}]}],
"SessionEnd": [{"hooks": [{"type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/cc_profiler.py _hook SessionEnd"}]}]
}
}
Don't add these unprompted — only suggest them if the user asks for finer-grained tool timing or asks how to extend the profiler.
Source: ventusff/claude-code-profiler — distributed by TomeVault.