vicnaum/vics-agent-skills/session-stripper

Claude Code and agent skill by vicnaum/vics-agent-skills

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Manually trim, compact, persist, and repair Claude Code conversation sessions stored as JSONL files. Use when the user wants to: (1) fix a session hitting 'Prompt is too long' errors, (2) manually compact or strip a session to reduce context, (3) analyze a session's token usage and message chain, (4) strip tool call inputs and results from a session, (5) strip thinking blocks from a session, (6) compact part of a session before a specific message, (7) verify or repair a broken session chain, (8) persist and summarize tool results to reduce context intelligently, (9) perform any JSONL-level surgery on Claude Code sessions. Triggers on mentions of session surgery, session trimming, session stripping, context compaction, session repair, prompt too long, session compact, manual session manipulation, summarize tool results, or persist tool output.

Install

npx vskill@latest install vicnaum/vics-agent-skills/session-stripper

SKILL.md

Session Surgery

CLI tool for trimming Claude Code JSONL sessions. Sessions live at ~/.claude/projects/<project-path>/<uuid>.jsonl.

Identify the session FIRST — never guess by mtime

Every mutating command operates on the exact <session.jsonl> path you pass. Getting that path right is the single most important step: strip the wrong file and you silently corrupt a different, possibly active, session.

To strip the CURRENT session, resolve it from `$CLAUDE_CODE_SESSION_ID` — the env var Claude Code exports into every Bash tool call, which matches the real transcript filename:

bash
SKILL=<skill-dir>/scripts/stripper.py
SESS=$(python3 "$SKILL" current --quiet) # resolves $CLAUDE_CODE_SESSION_ID → its .jsonl (any cwd)
python3 "$SKILL" strip-all "$SESS"

current (without --quiet) prints the id, path, and whether it exists.

NEVER select the session with `ls -t ... | head -1` (most-recently-modified). In a project folder with more than one session running — increasingly common with parallel/multi-agent work — the newest file is frequently a different live session, and you will strip someone else's work. $CLAUDE_CODE_SESSION_ID is the only reliable source; if it is unset you are not inside a CC session, so ask the user which file to strip rather than guessing.

When the user names a specific session id or file (e.g. "strip session abc123"), use that instead; current is only for "strip this/my session".

Copy-first: mutations never touch the real session file

Mutating commands do not write the session file you pass. They snapshot it to <session>.jsonl.pending, mutate only that copy, and print the apply hint. The original stays byte-identical until you run apply, which swaps the copy in with one atomic rename:

bash
python3 stripper.py strip-all <session.jsonl> # writes <session.jsonl>.pending, NOT the original
python3 stripper.py verify <session.jsonl>.pending
python3 stripper.py apply <session.jsonl> # atomic swap (only write to the real file)

Why (incident 2026-08-05): a running CC CLI flushes appends to its JSONL every 100ms, takes no lock, and --resume silently truncates the chain at the first missing parentUuid. Any in-place rewrite of a live session races those appends — one live compact-range collapsed a 763-message chain to 14. Copy-first makes that impossible at the architecture level: there is no in-place write left to race.

Mechanics:

  • Multi-pass strips reuse the same pending copy (analyze → compact-range → strip-tools → …), so iterate freely. snapshot --refresh discards it and re-snapshots.
  • apply accepts either path (original or .pending) and refuses to swap when: the original is being written right now (it grew during a 400ms settle check — a live CLI is appending), the original was rewritten since the snapshot (prefix-hash mismatch: the copy no longer descends from it — re-snapshot), or the pending copy fails `verify`. Refusals leave everything in place.
  • Anything appended to the original after the snapshot is discarded by apply, by design (counted and reported). In the self-strip flow that tail is the stripping turn itself — noise the session is better off without. Don't snapshot a session mid-way through work you care about.
  • The one .bak of the original is made at apply time. While mutating the pending copy no .baks are written — the untouched original is the backup.
  • --fork is unchanged and needs no apply: the fork is a brand-new file no CLI writes to, so it is mutated directly.
  • A "Prompt is too long" session can't run anything, so its file is quiescent even if the stuck CLI is still open — strip + apply immediately, then claude -r <id>.

Restarting into the stripped session (respawn)

Stripping shrinks the pending copy on disk, but the running CLI keeps its pre-strip context until it restarts — and for your OWN session the apply must happen while the CLI is dead. The `respawn` skill's watcher does exactly that: it /exits the CLI, waits for the process to die, applies the swap in that dead window, and relaunches:

  • In-place strip of the current session: strip (writes the pending copy), then respawn.sh --swap <session.jsonl>.pending. The watcher runs apply after process death; if apply refuses, it relaunches the UNSTRIPPED session and says so in the kickoff prompt (fail-open).
  • Forked strip (new session id from --fork): no swap needed — respawn.sh <new-session-id>.

This works the same for a Claude Code background session (claude --bg, or moved there with ← / /background): respawn stops it through the daemon, applies the swap and resumes the same job.

Call respawn.sh as the LAST action, then end the turn (see the respawn skill). Without respawn, the manual steps depend on where the session runs:

  • In a terminal: /exit, apply from any shell, claude -r <id>.
  • In the background (claude agents lists it, or $CLAUDE_JOB_DIR is set): /exit in an attached terminal only detaches, and claude -r refuses while it runs. Run claude stop <job>, wait a few seconds, apply, then claude attach <job> (or claude --bg --resume <id>).

Quick Reference

Run scripts from <skill-dir>/scripts/:

bash
# Analyze session — token breakdown by type and tool name, cut points, health check
python3 <skill-dir>/scripts/stripper.py analyze <session.jsonl>
# Strip all tool content + thinking (most common — frees 60-90% of context)
python3 <skill-dir>/scripts/stripper.py strip-all <session.jsonl>
# Strip only specific tools, keep last 3 lines of results
python3 <skill-dir>/scripts/stripper.py strip-tools <session.jsonl> --tools Bash,Read --only-results --keep-last-lines 3
# Strip thinking blocks only
python3 <skill-dir>/scripts/stripper.py strip-thinking <session.jsonl>
# Drop superseded attachments (system-reminders re-sent on EVERY request)
python3 <skill-dir>/scripts/stripper.py strip-attachments <session.jsonl> --list # what's there
python3 <skill-dir>/scripts/stripper.py strip-attachments <session.jsonl>
# Compact everything before chain position 150
python3 <skill-dir>/scripts/stripper.py compact <session.jsonl> --before 150
# Verify chain integrity
python3 <skill-dir>/scripts/stripper.py verify <session.jsonl>
# Copy-first plumbing: explicit snapshot; atomic swap of the pending copy
python3 <skill-dir>/scripts/stripper.py snapshot <session.jsonl> # optional — mutating commands auto-create it
python3 <skill-dir>/scripts/stripper.py apply <session.jsonl> # THE one write to the real file
python3 <skill-dir>/scripts/stripper.py apply <session.jsonl> --dry-run # run all apply checks, swap nothing
# Fix CC's context gauge without stripping (if a session is stripped but still reads ~full)
python3 <skill-dir>/scripts/stripper.py reset-usage <session.jsonl>

All commands support --dry-run (report only) and --no-backup (skip backup). Range filtering via --from N --to M (chain positions).

Backups are enumerated (never skipped)

apply (and any command that writes a real session file, e.g. mutating a .pending copy passed explicitly with backups on) writes a fresh backup: <session>.jsonl.bak, then .bak.1, .bak.2, … It never silently reuses or skips a backup just because one already exists. While mutating a pending copy no per-op backups are made — the untouched original is the recovery point; apply then creates the one real .bak at swap time. Pass --no-backup to opt out.

The context gauge resets automatically after stripping

CC's "context left" meter is not a live recount of the conversation — it reads the token counts (`input_tokens` + `cache_read_input_tokens` + `cache_creation_input_tokens`) recorded on the most recent assistant turn in the JSONL. Newer builds (CC 2.1.27x, Sep 2026) read the same three fields from the nested per-call `usage.iterations[]` list instead, so the reset nulls iterations (and zeroes cache_creation.ephemeral_*) on every record it touches — a stale iterations[0].cache_read_input_tokens alone is enough to keep a stripped session, and every fork of it, pinned at the pre-strip percentage. So stripping content alone does NOT move the meter: the stored numbers still describe the pre-strip size, the gauge stays pinned near 100%, and CC blocks new input client-side ("Context limit reached") even though the file is now tiny — the strip looks like it did nothing.

To fix this, strip-tools, strip-thinking, and strip-all now automatically rewrite the stale `usage` counts to the post-strip active-chain content estimate once stripping finishes. Every assistant turn whose recorded context exceeds the estimate is capped down to it (smaller turns are left alone), and the active chain's final assistant turn is pinned to the estimate — even if it recorded 0 (e.g. a blocked "Prompt is too long" turn). The next real turn re-establishes exact counts.

  • Opt out with --no-usage-reset.
  • Run it standalone with reset-usage on an already-stripped session whose meter is still stuck (no need to re-strip). It also accepts --fork.
  • The estimate counts message content and attachments (which CC re-sends every request). It also counts the system prompt + tool schemas when the session recorded them (prompt_snapshot, CC ≥ 2.1.267). Older sessions don't have that record, so for them the estimate is a floor. Either way, the first live turn corrects it precisely.
The gauge can only lie in your favour. Because the reset writes an estimate into the JSONL, a session that was barely stripped will still show a reassuring number — and then snap back to ~100% on the first real turn, which is the only number that was ever measured. If a strip freed little, the meter dropping is not evidence that it worked. Check what analyze says is left, and treat a real API turn (one with non-zero cache_read/cache_creation) as the only ground truth. Attachments were excluded from this estimate until they were found to be worth six figures of tokens on long sessions — that omission made the gauge read far below the true prompt size.
Preserved thinking: an edit drops every later thinking block

On Claude Fable 5.1, Opus 5.5, Sonnet 5.5 and Haiku 5.5, each thinking block's signature binds it to the history before it. It also binds it to the previous thinking block.

If you edit an earlier turn, the API drops every later thinking block. Claude Code runs the API in drop mode, so the request still succeeds, unbilled. The model just continues without that reasoning.

Safe: removing thinking blocks from the front of the history, as in strip-thinking --from 0 --to N.

Drops every later thinking block:

  • clearing tool results or tool inputs
  • persisting tools, text, messages or ranges
  • dropping attachments
  • compacting a range or the whole front
  • removing a thinking block from the middle

Every mutating command handles this for you. The code is in lib/preserved_thinking.py, and run_command calls it after each command:

  • It compares the live original with the result. It prints how many later thinking blocks the API will drop, their size, and the first chain position.
  • Dry runs preview this by running the command silently on a throwaway copy.
  • By default it removes those blocks from the result. The file and the context gauge then match what the model actually sees.
  • --keep-dropped-thinking keeps them. They cost no tokens either way.
  • Older models such as Opus 4.8 don't run the check, and neither does Claude Mythos 5.1. The step stays silent for them.

Practical rule: strip once, and deeply. Each mid-session strip wipes the reasoning built up since the previous one.

What Gets Stripped

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
43
Repository forks
3
7-day trend score
19

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 18 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/vicnaum/vics-agent-skills/session-stripper/badge)](https://verified-skill.com/skills/vicnaum/vics-agent-skills/session-stripper)

Something wrong with this skill or its page? Tell us.