|
| 1 | +# cc-session agent guide |
| 2 | + |
| 3 | +You are an LLM driving cc-session non-interactively. This guide is the |
| 4 | +single source of truth for how to use it. Read it once, then operate. |
| 5 | + |
| 6 | +## What this CLI does |
| 7 | + |
| 8 | +Edits Claude Code session JSONL files at `~/.claude/projects/<slug>/<uuid>.jsonl`. |
| 9 | +It can browse, search, inspect, and surgically delete messages from any session |
| 10 | +while keeping `tool_use` / `tool_result` pairs and conversational turns intact. |
| 11 | + |
| 12 | +**Important behavioral note (v1+):** `delete` NEVER mutates the source file. |
| 13 | +It always writes a NEW session file with a fresh UUID and prints a |
| 14 | +`claude --resume <new-id>` command. The original is never touched. There is |
| 15 | +no `--force` flag, no lsof check, no `.bak` file — none of those are needed |
| 16 | +when forking. |
| 17 | + |
| 18 | +## Standard workflow |
| 19 | + |
| 20 | +1. Discover sessions: |
| 21 | + ```sh |
| 22 | + cc-session list --json --limit 20 |
| 23 | + cc-session search "<query>" --json --limit 10 |
| 24 | + ``` |
| 25 | +2. Inspect one session: |
| 26 | + ```sh |
| 27 | + cc-session info <id-or-path> --json |
| 28 | + cc-session show <id-or-path> --json |
| 29 | + ``` |
| 30 | +3. Find heaviest turns to drop: |
| 31 | + ```sh |
| 32 | + cc-session heatmap <id-or-path> --json --limit 10 |
| 33 | + ``` |
| 34 | +4. Plan an edit (always dry-run first): |
| 35 | + ```sh |
| 36 | + cc-session delete <id> --indices 4,6 --dry-run --json |
| 37 | + ``` |
| 38 | +5. Apply (writes a new session file, original untouched): |
| 39 | + ```sh |
| 40 | + cc-session delete <id> --indices 4,6 --json |
| 41 | + ``` |
| 42 | + Output includes `new_session_id`, `new_path`, `resume_command`. |
| 43 | +6. (Optional) self-update: |
| 44 | + ```sh |
| 45 | + cc-session update [--version v1.0.0] |
| 46 | + ``` |
| 47 | + |
| 48 | +## Target argument (`<id-or-path>`) |
| 49 | + |
| 50 | +For `show` / `info` / `heatmap` / `delete` the first positional arg accepts: |
| 51 | + |
| 52 | +- a full filesystem path to a `.jsonl` file |
| 53 | +- a full session UUID (preferred — unambiguous) |
| 54 | +- any unique substring of a session UUID (8+ chars usually fine) |
| 55 | + |
| 56 | +If a substring matches multiple sessions, the command errors and lists the |
| 57 | +candidates. Pass a longer prefix to disambiguate. |
| 58 | + |
| 59 | +## Index semantics |
| 60 | + |
| 61 | +Indices are 0-based positions in the raw JSONL (one per line). Use |
| 62 | +`cc-session show --json` to map message text → index. Note: |
| 63 | + |
| 64 | +- "Visible" messages (user / assistant text) are a subset; system messages, |
| 65 | + `tool_use` blocks, `tool_result` blocks, attachments, and harness wrappers |
| 66 | + (`<bash-input>`, `<system-reminder>`, etc.) are hidden by default. Pass |
| 67 | + `--include-hidden` to see them in `show`. |
| 68 | +- Indices in the SOURCE session are stable across deletes (because deletes |
| 69 | + fork instead of mutating). Each new fork has its own index space — if you |
| 70 | + chain edits, re-run `show` against the new session id. |
| 71 | + |
| 72 | +## Auto-pair (always on) |
| 73 | + |
| 74 | +Two safety extensions run on every delete request: |
| 75 | + |
| 76 | +1. `tool_use` ↔ `tool_result` blocks always travel together. Marking either |
| 77 | + side pulls the other. |
| 78 | +2. Turn-level pairing: a "turn" = visible user msg + every message that |
| 79 | + follows it until the next visible user msg. Marking ANY message in a |
| 80 | + turn marks the whole turn (user prompt + assistant reply + intermediate |
| 81 | + tool calls). |
| 82 | + |
| 83 | +The delete output reports `requested` (what you asked) and `paired_added` |
| 84 | +(what auto-pair added). Always inspect both before applying. |
| 85 | + |
| 86 | +## Resume safety: parentUuid auto-relink |
| 87 | + |
| 88 | +Every fork rewrites surviving messages whose `parentUuid` would point to |
| 89 | +a deleted ancestor, walking up to the nearest surviving ancestor (or null |
| 90 | +at the root). Reported as `parent_uuid_relinked`. Foreign parent uuids |
| 91 | +(referring to messages not in the file) are preserved verbatim. |
| 92 | + |
| 93 | +## `delete` output JSON |
| 94 | + |
| 95 | +```json |
| 96 | +{ |
| 97 | + "source_path": "<absolute path of input session>", |
| 98 | + "new_session_id": "<uuid>", |
| 99 | + "new_path": "<absolute path of fork>", |
| 100 | + "resume_command": "claude --resume <uuid>", |
| 101 | + "parent_uuid_relinked": 0, |
| 102 | + "requested": [], |
| 103 | + "after_auto_pair": [], |
| 104 | + "paired_added": [], |
| 105 | + "total_messages_before": 0, |
| 106 | + "total_messages_after": 0, |
| 107 | + "dry_run": false, |
| 108 | + "saved": true, |
| 109 | + "warnings": [] |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +`new_session_id`, `new_path`, `resume_command` are populated even in dry-run |
| 114 | +(preview values). `saved` is `true` when the fork file was actually written. |
| 115 | + |
| 116 | +## `show` output JSON (per message) |
| 117 | + |
| 118 | +```json |
| 119 | +{ |
| 120 | + "index": 0, |
| 121 | + "role": "user", |
| 122 | + "type": "user", |
| 123 | + "timestamp": "2026-06-12T00:00:00Z", |
| 124 | + "tokens": 0, |
| 125 | + "visible": true, |
| 126 | + "has_tool_use": false, |
| 127 | + "has_tool_result": false, |
| 128 | + "tool_use_ids": [], |
| 129 | + "tool_result_ids": [], |
| 130 | + "text": "...", |
| 131 | + "truncated": false |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +`tokens` is tiktoken `cl100k_base` counted on the WHOLE raw JSONL line |
| 136 | +(text + `tool_use` input + `tool_result` content + metadata). `text` is a |
| 137 | +400-char preview by default; pass `--full` to get the full body. |
| 138 | + |
| 139 | +## `info` output JSON |
| 140 | + |
| 141 | +```json |
| 142 | +{ |
| 143 | + "path": "...", "project": "...", "session_id": "...", "title": "...", |
| 144 | + "modified": "...", "size": 0, |
| 145 | + "is_fork": false, |
| 146 | + "fork_origin": null, |
| 147 | + "total_messages": 0, "visible_messages": 0, |
| 148 | + "user_messages": 0, "assistant_messages": 0, |
| 149 | + "tool_use_count": 0, "tool_result_count": 0, |
| 150 | + "orphan_result_indices": [], |
| 151 | + "estimated_tokens": 0 |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +`estimated_tokens` is the sum of true per-msg counts. |
| 156 | + |
| 157 | +## `heatmap` output JSON |
| 158 | + |
| 159 | +```json |
| 160 | +{ |
| 161 | + "path": "<absolute path>", |
| 162 | + "session_id": "<uuid>", |
| 163 | + "total_messages": 0, |
| 164 | + "total_tokens": 0, |
| 165 | + "turns": [ |
| 166 | + { |
| 167 | + "anchor_idx": 0, |
| 168 | + "start_idx": 0, |
| 169 | + "end_idx": 0, |
| 170 | + "msg_count": 0, |
| 171 | + "tokens": 0, |
| 172 | + "has_tool_use": false, |
| 173 | + "preview": "..." |
| 174 | + } |
| 175 | + ] |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +`turns` is sorted by `tokens` descending. Drop the heaviest first. |
| 180 | + |
| 181 | +## `list` / `search` output JSON (per entry) |
| 182 | + |
| 183 | +```json |
| 184 | +{ |
| 185 | + "project": "...", "session_id": "...", "title": "...", |
| 186 | + "modified": "...", "size": 0, "path": "...", |
| 187 | + "is_fork": false, |
| 188 | + "fork_origin": null |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +`title` carries an `[edited] ` prefix when `is_fork` is true. |
| 193 | + |
| 194 | +## Selection flags for `delete` |
| 195 | + |
| 196 | +You may combine any/all; the union is taken before auto-pair runs. |
| 197 | + |
| 198 | +``` |
| 199 | +--indices 3,5,7 # exact indices (comma-separated) |
| 200 | +--range lo..hi # inclusive range, both ints |
| 201 | +--from-top N # first N messages |
| 202 | +--from-bottom N # last N messages |
| 203 | +``` |
| 204 | + |
| 205 | +At least one selection flag is required. |
| 206 | + |
| 207 | +## Exit codes |
| 208 | + |
| 209 | +| Code | Meaning | |
| 210 | +|------|---------| |
| 211 | +| `0` | success | |
| 212 | +| `1` | generic error (parse failure, IO error, ambiguous target, ...) | |
| 213 | +| `2+` | reserved for future structured errors | |
| 214 | + |
| 215 | +Always inspect stderr on non-zero exit for the human-readable cause. |
| 216 | + |
| 217 | +## Environment overrides |
| 218 | + |
| 219 | +| Var | Effect | |
| 220 | +|-----|--------| |
| 221 | +| `CC_SESSION_VERSION` | pin a specific release (used by `update`) | |
| 222 | +| `CC_SESSION_INSTALL_DIR` | where `install.sh` drops the binary | |
| 223 | +| `CC_SESSION_INSTALLER_URL` | override installer URL for `update` (testing) | |
| 224 | + |
| 225 | +## End-to-end recipe (real run, copy this shape) |
| 226 | + |
| 227 | +```sh |
| 228 | +# 1. Locate the session id. Inside Claude Code: /status -> Session ID. |
| 229 | +cc-session list --json --limit 10 |
| 230 | + |
| 231 | +# 2. See the wire size. estimated_tokens reflects whole-line tiktoken |
| 232 | +# (text + tool_use args + tool_result content + metadata) — usually |
| 233 | +# 2-3x larger than what plain message text would suggest. |
| 234 | +cc-session info <id> --json |
| 235 | + |
| 236 | +# 3. Find the heaviest CONVERSATIONAL TURNS. A turn rolls up the user |
| 237 | +# prompt + every assistant/tool message it triggered up to the next |
| 238 | +# visible user prompt. This matches what auto-pair will delete. |
| 239 | +cc-session heatmap <id> --json --limit 10 |
| 240 | + |
| 241 | +# 4. Pick a contiguous block of turns that are clearly noise (long |
| 242 | +# iteration loops, exploratory tool dumps, repeated re-reviews of |
| 243 | +# the same doc, etc). Prefer one --range over many --indices: it's |
| 244 | +# less likely to leave parentUuid orphans, and even when it does, |
| 245 | +# auto-relink fixes them (and reports parent_uuid_relinked). |
| 246 | +cc-session delete <id> --range <lo>..<hi> --dry-run --json |
| 247 | + |
| 248 | +# 5. Apply. The output's `resume_command` is ready to paste. |
| 249 | +cc-session delete <id> --range <lo>..<hi> --json |
| 250 | + |
| 251 | +# 6. Resume the NEW id in Claude Code: |
| 252 | +# claude --resume <new_session_id> |
| 253 | +# |
| 254 | +# The new id forces a fresh prefix-cache slot, so Claude Code's |
| 255 | +# /context immediately reflects the smaller size — no stale cache. |
| 256 | +``` |
| 257 | + |
| 258 | +## Useful examples (one-liners) |
| 259 | + |
| 260 | +```sh |
| 261 | +# find the heaviest turns and drop the worst three |
| 262 | +cc-session heatmap <id> --json --limit 5 |
| 263 | +cc-session delete <id> --indices <a>,<b>,<c> --dry-run --json |
| 264 | +cc-session delete <id> --indices <a>,<b>,<c> --json |
| 265 | + |
| 266 | +# delete top 50 messages of a long session, dry run first |
| 267 | +cc-session delete <id> --from-top 50 --dry-run --json |
| 268 | + |
| 269 | +# purge messages 200..280 inclusive |
| 270 | +cc-session delete <id> --range 200..280 --dry-run --json |
| 271 | + |
| 272 | +# find a session about "auth middleware" and inspect |
| 273 | +cc-session search "auth middleware" --json --limit 1 |
| 274 | +cc-session show <id-from-above> --json |
| 275 | + |
| 276 | +# chain edits: each delete produces a new id; pass that id back to |
| 277 | +# cc-session for the next trim. Forks are marked is_fork=true in list. |
| 278 | +cc-session delete <new_id_from_step_5> --range ... --json |
| 279 | +``` |
| 280 | + |
| 281 | +## Things this CLI will NOT do |
| 282 | + |
| 283 | +- Edit message contents in place. |
| 284 | +- Reorder messages. |
| 285 | +- Merge or split sessions. |
| 286 | +- Mutate the source session (every delete forks). |
0 commit comments