You are an LLM driving cc-session non-interactively. This guide is the single source of truth for how to use it. Read it once, then operate.
Edits Claude Code session JSONL files at ~/.claude/projects/<slug>/<uuid>.jsonl.
It can browse, search, inspect, and surgically delete messages from any session
while keeping tool_use / tool_result pairs and conversational turns intact.
Important behavioral note (v1+): delete NEVER mutates the source file.
It always writes a NEW session file with a fresh UUID and prints a
claude --resume <new-id> command. The original is never touched. There is
no --force flag, no lsof check, no .bak file — none of those are needed
when forking.
- Discover sessions:
cc-session list --json --limit 20 cc-session search "<query>" --json --limit 10 - Inspect one session:
cc-session info <id-or-path> --json cc-session show <id-or-path> --json
- Find heaviest turns to drop:
cc-session heatmap <id-or-path> --json --limit 10
- Plan an edit (always dry-run first):
cc-session delete <id> --indices 4,6 --dry-run --json
- Apply (writes a new session file, original untouched):
Output includes
cc-session delete <id> --indices 4,6 --json
new_session_id,new_path,resume_command. - (Optional) self-update:
cc-session update [--version v1.0.0]
For show / info / heatmap / delete the first positional arg accepts:
- a full filesystem path to a
.jsonlfile - a full session UUID (preferred — unambiguous)
- any unique substring of a session UUID (8+ chars usually fine)
If a substring matches multiple sessions, the command errors and lists the candidates. Pass a longer prefix to disambiguate.
Indices are 0-based positions in the raw JSONL (one per line). Use
cc-session show --json to map message text → index. Note:
- "Visible" messages (user / assistant text) are a subset; system messages,
tool_useblocks,tool_resultblocks, attachments, and harness wrappers (<bash-input>,<system-reminder>, etc.) are hidden by default. Pass--include-hiddento see them inshow. - Indices in the SOURCE session are stable across deletes (because deletes
fork instead of mutating). Each new fork has its own index space — if you
chain edits, re-run
showagainst the new session id.
Two safety extensions run on every delete request:
tool_use↔tool_resultblocks always travel together. Marking either side pulls the other.- Turn-level pairing: a "turn" = visible user msg + every message that follows it until the next visible user msg. Marking ANY message in a turn marks the whole turn (user prompt + assistant reply + intermediate tool calls).
The delete output reports requested (what you asked) and paired_added
(what auto-pair added). Always inspect both before applying.
Every fork rewrites surviving messages whose parentUuid would point to
a deleted ancestor, walking up to the nearest surviving ancestor (or null
at the root). Reported as parent_uuid_relinked. Foreign parent uuids
(referring to messages not in the file) are preserved verbatim.
{
"source_path": "<absolute path of input session>",
"new_session_id": "<uuid>",
"new_path": "<absolute path of fork>",
"resume_command": "claude --resume <uuid>",
"parent_uuid_relinked": 0,
"requested": [],
"after_auto_pair": [],
"paired_added": [],
"total_messages_before": 0,
"total_messages_after": 0,
"dry_run": false,
"saved": true,
"warnings": []
}new_session_id, new_path, resume_command are populated even in dry-run
(preview values). saved is true when the fork file was actually written.
{
"index": 0,
"role": "user",
"type": "user",
"timestamp": "2026-06-12T00:00:00Z",
"tokens": 0,
"visible": true,
"has_tool_use": false,
"has_tool_result": false,
"tool_use_ids": [],
"tool_result_ids": [],
"text": "...",
"truncated": false
}tokens is tiktoken cl100k_base counted on the WHOLE raw JSONL line
(text + tool_use input + tool_result content + metadata). text is a
400-char preview by default; pass --full to get the full body.
{
"path": "...", "project": "...", "session_id": "...", "title": "...",
"modified": "...", "size": 0,
"is_fork": false,
"fork_origin": null,
"total_messages": 0, "visible_messages": 0,
"user_messages": 0, "assistant_messages": 0,
"tool_use_count": 0, "tool_result_count": 0,
"orphan_result_indices": [],
"estimated_tokens": 0
}estimated_tokens is the sum of true per-msg counts.
{
"path": "<absolute path>",
"session_id": "<uuid>",
"total_messages": 0,
"total_tokens": 0,
"turns": [
{
"anchor_idx": 0,
"start_idx": 0,
"end_idx": 0,
"msg_count": 0,
"tokens": 0,
"has_tool_use": false,
"preview": "..."
}
]
}turns is sorted by tokens descending. Drop the heaviest first.
{
"project": "...", "session_id": "...", "title": "...",
"modified": "...", "size": 0, "path": "...",
"is_fork": false,
"fork_origin": null
}title carries an [edited] prefix when is_fork is true.
You may combine any/all; the union is taken before auto-pair runs.
--indices 3,5,7 # exact indices (comma-separated)
--range lo..hi # inclusive range, both ints
--from-top N # first N messages
--from-bottom N # last N messages
At least one selection flag is required.
| Code | Meaning |
|---|---|
0 |
success |
1 |
generic error (parse failure, IO error, ambiguous target, ...) |
2+ |
reserved for future structured errors |
Always inspect stderr on non-zero exit for the human-readable cause.
| Var | Effect |
|---|---|
CC_SESSION_VERSION |
pin a specific release (used by update) |
CC_SESSION_INSTALL_DIR |
where install.sh drops the binary |
CC_SESSION_INSTALLER_URL |
override installer URL for update (testing) |
# 1. Locate the session id. Inside Claude Code: /status -> Session ID.
cc-session list --json --limit 10
# 2. See the wire size. estimated_tokens reflects whole-line tiktoken
# (text + tool_use args + tool_result content + metadata) — usually
# 2-3x larger than what plain message text would suggest.
cc-session info <id> --json
# 3. Find the heaviest CONVERSATIONAL TURNS. A turn rolls up the user
# prompt + every assistant/tool message it triggered up to the next
# visible user prompt. This matches what auto-pair will delete.
cc-session heatmap <id> --json --limit 10
# 4. Pick a contiguous block of turns that are clearly noise (long
# iteration loops, exploratory tool dumps, repeated re-reviews of
# the same doc, etc). Prefer one --range over many --indices: it's
# less likely to leave parentUuid orphans, and even when it does,
# auto-relink fixes them (and reports parent_uuid_relinked).
cc-session delete <id> --range <lo>..<hi> --dry-run --json
# 5. Apply. The output's `resume_command` is ready to paste.
cc-session delete <id> --range <lo>..<hi> --json
# 6. Resume the NEW id in Claude Code:
# claude --resume <new_session_id>
#
# The new id forces a fresh prefix-cache slot, so Claude Code's
# /context immediately reflects the smaller size — no stale cache.# find the heaviest turns and drop the worst three
cc-session heatmap <id> --json --limit 5
cc-session delete <id> --indices <a>,<b>,<c> --dry-run --json
cc-session delete <id> --indices <a>,<b>,<c> --json
# delete top 50 messages of a long session, dry run first
cc-session delete <id> --from-top 50 --dry-run --json
# purge messages 200..280 inclusive
cc-session delete <id> --range 200..280 --dry-run --json
# find a session about "auth middleware" and inspect
cc-session search "auth middleware" --json --limit 1
cc-session show <id-from-above> --json
# chain edits: each delete produces a new id; pass that id back to
# cc-session for the next trim. Forks are marked is_fork=true in list.
cc-session delete <new_id_from_step_5> --range ... --json- Edit message contents in place.
- Reorder messages.
- Merge or split sessions.
- Mutate the source session (every delete forks).