Full session transcripts from Claude Code — including content lost to terminal scrollback and context window compaction.
Claude Code stores complete conversation history as JSONL files locally on your machine. This tool converts those logs into clean, readable markdown transcripts with three levels of detail.
If you've tried to save a Claude Code session transcript, you've probably hit one of these walls:
Two changes to Claude Code's rendering engine broke terminal-based transcript capture:
Regression 1 — Scrollback Truncation (v2.1.76+)
Claude Code switched to an internal render buffer that caps visible output at roughly 300 lines. In a 2-hour session producing thousands of lines, the first 90%+ is silently discarded from the terminal. Your terminal emulator and tmux never see it.
Regression 2 — Alternate Screen Buffer (v2.1.89+)
Claude Code began using an alternate terminal screen (like vim or htop). When you exit, the entire conversation vanishes from your terminal scrollback. tmux capture-pane captures a blank buffer. Your session is gone.
Anthropic provides environment variables to modify the rendering behavior:
| Variable | What It Does | Trade-off |
|---|---|---|
CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 |
Restores native terminal scrollback | Still capped at ~300 lines (Regression 1 remains) |
CLAUDE_CODE_NO_FLICKER=1 |
Fullscreen mode — no flicker, flat memory | Alternate screen — tmux capture gets nothing |
CLAUDE_CODE_DISABLE_MOUSE=1 |
Keyboard scrolling in fullscreen mode | Only useful with NO_FLICKER; doesn't help capture |
No combination of these variables gives both flicker-free rendering AND full terminal capture. You can have one or the other, not both. We tested all six combinations — see Environment Variable Reference below.
Claude Code has a built-in /export that writes a compact transcript. It works, but:
- No tool output — you see that a Bash command ran, but not what it returned
- No edit diffs — you see a file was edited, but not what changed
- No search results — you see a search happened, but not what was found
- A 2-hour session that produces 2,000+ lines in detailed mode exports as ~500 lines
For many use cases that's fine. But if you need to review what actually happened — what commands returned, what code was changed, what the AI decided and why — you need the full picture.
Claude Code stores the complete conversation history as JSONL files at ~/.claude/projects/. These files contain every message from every session — every tool call, every result, every edit diff. They survive terminal scrollback limits. They survive context window compaction. Nothing is lost.
This tool converts those JSONL files into clean, readable markdown transcripts with three levels of detail, from compact summaries to full verbatim output.
Three output modes to match how you want to review sessions:
Compact (default) — tool calls collapsed to one line with result summaries:
> Build me a REST API for the todo app
> Bash(npm init -y && npm install express)
| (No output)
> Write(/src/index.js)
| File written
> Bash(node src/index.js &)
| Server listening on port 3000
Detailed — tool results expanded, search hits listed, edit diffs shown:
> Edit(/src/index.js)
| - app.get('/todos', ...)
| + app.get('/todos', authenticate, ...)
> Search(pattern: "authenticate", path: "src/")
| Found 3 files
src/middleware/auth.js
src/index.js
src/routes/todos.js
Full — the complete record. Every file that was read, every line of command output, every search result, every edit diff — nothing truncated, nothing summarized:
> Read(/src/middleware/auth.js)
1 const jwt = require('jsonwebtoken');
2
3 function authenticate(req, res, next) {
4 const token = req.headers.authorization?.split(' ')[1];
5 if (!token) return res.status(401).json({ error: 'No token' });
...
> Bash(npm test)
PASS src/tests/auth.test.js authenticate middleware ✓ rejects missing token (3ms) ✓ rejects invalid token (2ms) ✓ passes valid token (4ms)
Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total
Full mode is the audit trail — you can reconstruct exactly what Claude saw, what it did, and what every tool returned. A 2-hour session produces ~5,000-6,000 lines in full mode.
| Compact | Detailed | Full | |
|---|---|---|---|
| Conversation text | Yes | Yes | Yes |
| Tool call names | Yes | Yes | Yes |
| Tool result summaries | One-line | Multi-line | Complete |
| Edit diffs | No | Yes | Yes |
| Search result lists | No | Yes | Yes |
| File contents read | No | No | Yes |
| Full command output | No | Truncated | Yes |
| Typical size (2hr session) | ~1,500 lines | ~2,000 lines | ~6,000 lines |
| Best for | Quick review | Code review | Audit trail |
The most practical setup is to auto-export compact + full (or all three) on every session. Storage is cheap — a full-mode transcript is typically under 500KB. Having all modes available means you can reach for the right one without re-running the converter.
Compact — your daily driver. Skim it to remember what happened. Share it with a colleague. Feed it into a new Claude session as context for continuation work. At ~1,500 lines it fits comfortably in a context window without burning excessive tokens.
Detailed — your code review companion. When you need to review what changed, detailed mode shows the edit diffs and command output without overwhelming you with full file reads. Good for PR descriptions, session handoffs, and debugging "what did the AI actually do?"
Full — your archive and search target. You probably won't read a 6,000-line transcript end to end. But when you need to find something specific — a config value that was read, a test output, an error message — grep is your friend:
# Find every file that was read during a session
grep "^⏺ Read(" session-full.md
# Find all Bash commands and their output
grep -A 10 "^⏺ Bash(" session-full.md
# Search for a specific error across all archived sessions
grep -r "ConnectionRefused" ~/transcripts/*-full.md
# Find which session edited a specific file
grep -l "Edit(/src/auth" ~/transcripts/*-detailed.md
# Pull the full context around a specific change
grep -B 2 -A 20 "old_string.*authenticate" session-full.mdThis is where full mode earns its keep. You don't read it — you search it. The compact transcript tells you which session to look at; the full transcript has the actual content when you need to dig in.
The converter reads JSONL files that are already on your disk — it makes zero API calls and costs nothing to run. But if you feed a transcript back into Claude (as context for a new session, for example), the transcript size directly impacts token usage:
| Mode | Typical Size | Estimated Tokens | Context Impact |
|---|---|---|---|
| Compact | ~1,500 lines | ~8,000-12,000 tokens | Fits easily. Good for "continue where I left off" |
| Detailed | ~2,000 lines | ~12,000-18,000 tokens | Moderate. Good for targeted review |
| Full | ~6,000 lines | ~40,000-60,000 tokens | Large. Use grep to extract relevant sections first |
Practical guidance:
- Feeding a transcript to Claude for continuation: Use compact. It has everything Claude needs to understand what happened without spending half your context window on file contents it can re-read.
- Reviewing code changes with Claude: Use detailed. The diffs give Claude (and you) enough to reason about what changed without the full file reads.
- Searching for something specific: Use full + grep locally. Pull out the relevant 50 lines and feed those into Claude, not the entire transcript.
- Long-term archival: Export all three. Disk is cheap. You can always grep the full version later when you need it, and the compact version stays human-readable for quick reference.
The auto-export hook runs all selected modes in parallel, so generating all three takes roughly the same time as generating one.
git clone https://github.com/georgesamuelson/claude-session-transcript.git
cd claude-session-transcript
./install.shThe installer walks you through:
- Which modes to auto-export (compact, detailed, full, all, or none)
- Where to save transcripts (per-project or centralized)
- Slash command — installs
/transcriptfor use inside sessions - Auto-export hook — generates transcripts when sessions end
Re-run ./install.sh anytime to change your settings. Config is stored at ~/.claude-transcript.conf.
Important: After installing, restart Claude Code (exit and start a new session). Hooks are loaded at session start — any session that was already running won't pick up the new auto-export hook. Continued/resumed sessions from before installation also won't auto-export.
By default, Claude Code will prompt you to approve the Bash command each time /transcript runs. To skip the prompt, add this to your ~/.claude/settings.json:
{
"allowedTools": ["Bash(python3:*)"]
}Or if you already have an allowedTools array, add "Bash(python3:*)" to it. This allows the transcript converter (which runs via python3) to execute without prompting.
Note: This setting takes effect on your next session — it won't apply to any session that's already running. Also,
Bash(python3:*)approves any command starting withpython3, not just the transcript converter.
Copy the skill into your Claude Code skills directory:
cp -r claude-session-transcript/skills/transcript ~/.claude/skills/claude-session-transcriptThen inside any Claude Code session, type:
/transcript # compact (default)
/transcript detailed # single mode
/transcript full ./my-session.md # with custom output path
/transcript compact detailed # generate both
/transcript all # generate compact + detailed + full
/transcript all ./session.md # all three with custom base name
The folder name is just for your reference — the slash command name comes from the
name: transcriptfield in SKILL.md.
Add the hook to your ~/.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/path/to/claude-session-transcript/hooks/session-auto-transcript.sh",
"async": true
}
]
}
]
}
}Note: If you already have
Stophooks, add the new hook entry to your existing array. Theasync: trueflag is required — without it the hook would block between responses.
How the hook handles Stop-after-every-response: Claude Code fires Stop hooks after every assistant response, not just on session exit. The hook uses a process-based gate: each instance writes its PID to a marker file and polls every 5 seconds. If a newer hook supersedes it (the session is still going), the older one exits without generating. Generation only happens when the parent Claude Code process actually dies — there is no time-based fallback, so idle sessions never produce spurious files. Files appear within seconds of the real exit.
Configure with environment variables (optional):
export TRANSCRIPT_MODES="compact detailed" # any combination, or "all"
export TRANSCRIPT_DIR=~/transcripts # default: ./transcripts/ in project dirgit clone https://github.com/georgesamuelson/claude-session-transcript.git
cd claude-session-transcript
# Convert a specific session:
python3 transcript.py ~/.claude/projects/-Users-you-Projects-myapp/abc123.jsonl output.md
# Auto-find the latest session for a project:
python3 transcript.py --latest myapp output.md
# Detailed mode:
python3 transcript.py --detailed --latest myapp output-detailed.md
# List all available sessions:
python3 transcript.py --list
python3 transcript.py --list myappClaude Code stores JSONL files at:
~/.claude/projects/{project-hash}/{session-id}.jsonl
The project hash is your working directory with / replaced by -:
/Users/you/Projects/myapp → -Users-you-Projects-myapp
Use --list to see all available sessions:
python3 transcript.py --listWhen a Claude Code session runs long, the context window gets compacted — older messages are summarized to make room for new ones. This is normal and expected.
The JSONL files are not affected by compaction. Every message from the entire session is preserved in the JSONL, including all the content that was summarized away. This tool converts the full, uncompacted history.
The transcript header shows how many compaction events occurred:
# Session Transcript — myapp
**Model:** claude-sonnet-4-20250514 | **Branch:** main | **Started:** 02:15 PM
**Compactions:** 3
- Python 3.6+
- No external dependencies (pure standard library)
Before the scrollback regressions, the best way to capture Claude Code sessions was tmux:
# Start Claude Code inside tmux with unlimited scrollback
tmux new-session -s claude
# (set scrollback high in .tmux.conf: set-option -g history-limit 50000)
claude
# Capture the full session at any point:
tmux capture-pane -t claude -p -S - > session.md
# Or capture with timestamp:
tmux capture-pane -t claude -p -S - > "session-$(date +%Y%m%d-%H%M).md"This produced transcripts with the full visual formatting — Unicode box characters, colored output indicators, the actual terminal experience. If Anthropic restores reliable scrollback in a future version, this method is still preferred for its fidelity.
These are two different needs:
- Live scrollback — "I want to scroll up in my terminal and see what just happened." This requires env vars and depends on your terminal setup.
- Transcript export — "I want a saved file of the full session." This is what claude-session-transcript does, and it works regardless of your env var or terminal configuration.
If you need live scrollback (e.g., you use tmux + Ghostty, iTerm2, or any terminal with a scrollback buffer):
# Add to your .zshrc or .bashrc:
export CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1This restores native terminal scrollback so you can scroll up in tmux, use Cmd+F to search, and tmux capture-pane to grab content. The trade-off is the ~300-line internal buffer cap (Regression 1) still applies, so very long sessions will lose early content from the terminal — but you can see and interact with the recent history live.
If you don't need live scrollback and just want saved transcripts, you don't need any env vars. Use CLAUDE_CODE_NO_FLICKER=1 for the best rendering experience and let the auto-export hook or /transcript handle the rest.
The ideal setup (both):
# Best rendering in terminal
export CLAUDE_CODE_NO_FLICKER=1
# Auto-export handles the transcript
# (hook fires on session end, or use /transcript mid-session)Once Anthropic addresses the scrollback buffer cap (GitHub issue context), users who prefer tmux capture can switch back to CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 and get both live scrollback AND full-length capture. When that happens, the tmux method becomes the preferred primary approach again, with JSONL export as the safety net for compaction recovery.
Use both methods together:
- tmux for visual fidelity and live scrollback (when the buffer works)
- JSONL converter for completeness (always works, survives compaction)
The JSONL converter is the safety net. tmux is the premium experience.
After installing the skill, you can edit ~/.claude/skills/transcript/SKILL.md to change:
- Default mode: Change "Default:
compact" to your preferred mode - Default output path: Change
session-transcript.mdto your preferred filename pattern
The easiest way to configure is ./install.sh. It writes ~/.claude-transcript.conf:
# ~/.claude-transcript.conf
modes=compact detailed
dir=/Users/you/transcripts
script=/path/to/transcript.pyEnvironment variables override the config file if both are set:
export TRANSCRIPT_MODES="compact detailed" # any combination, or "all"
export TRANSCRIPT_DIR=~/transcripts # centralized transcript directory
export TRANSCRIPT_SCRIPT=/path/to/transcript.py # if not in default locationsThe default naming pattern is:
{project}-session-{seq}-{id}-{timestamp}.md
Example: myapp-session-001-ae4aaaf7-20260409-1415.md
The {seq} token auto-increments based on existing files in the output directory. If your last transcript was session-042, the next one is session-043. Session numbers are zero-padded to three digits (001, 002, ... 999) and expand naturally to four digits at 1000+. It also checks your project's sessions/md/ directory to stay in sync with existing session numbering.
You can customize this during ./install.sh or by editing ~/.claude-transcript.conf:
# Available tokens: {project} {seq} {timestamp} {date} {id}
# {seq} auto-increments (01, 02, 03...)
# {id} is the first 8 chars of the JSONL session UUID
filename={project}-session-{seq}-{id}-{timestamp}Preset options from the installer:
| Option | Pattern | Example |
|---|---|---|
| 1 (default) | {project}-session-{seq}-{id}-{timestamp} |
myapp-session-001-ae4aaaf7-20260409-1415.md |
| 2 | {project}-session-{seq}-{timestamp} |
myapp-session-001-20260409-1415.md |
| 3 | {date}-{project}-{seq} |
2026-04-09-myapp-01.md |
| 4 | Custom | Whatever you want |
Naming convention: Compact is the base transcript — it never gets a suffix. Detailed and full always get a -detailed or -full suffix. This applies whether you generate one mode or all three:
myapp-session-014-ae4aaaf7-20260409-1415.md ← compact (the transcript)
myapp-session-014-ae4aaaf7-20260409-1415-detailed.md ← detailed variant
myapp-session-014-ae4aaaf7-20260409-1415-full.md ← full variant
Git worktree sessions are automatically detected and routed to a subdirectory:
sessions/md/ ← parent sessions
sessions/md/worktrees/
├── r2-worker/
│ ├── ea-jorts-r2-worker-session-001-ae4aaaf7-20260409-1415.md
│ └── ea-jorts-r2-worker-session-001-ae4aaaf7-20260409-1415-detailed.md
├── gs-book/
│ └── ea-jorts-gs-book-session-001-bf9a90a1-20260409-1530.md
└── sw-financing/
└── ...
How it works:
- The hook detects worktrees by checking if
.gitis a file (worktree) vs a directory (main repo) - Worktree transcripts go to
{main-project-dir}/{output-dir}/worktrees/{worktree-name}/ - The
{project}token becomes{main-project}-{worktree-name}(e.g.,ea-jorts-r2-worker) - Session numbers (
{seq}) auto-increment within each worktree's directory independently - The worktree subdirectory name defaults to
worktreesand is configurable viaTRANSCRIPT_WORKTREE_DIR
Key detail: Worktree transcripts always route to the main project's output directory, not the worktree's own CWD. If your config says dir=sessions/md, a worktree session writes to {main-project}/sessions/md/worktrees/{name}/, not {worktree-cwd}/sessions/md/.
This keeps all session history centralized in the main project, regardless of where git worktrees live on disk.
"No session JSONL found for this project directory"
- The JSONL path is derived from your CWD:
/Users/you/Projects/myapp→~/.claude/projects/-Users-you-Projects-myapp/ - Make sure you're in the same directory you were when you ran Claude Code
- Use
python3 transcript.py --listto see all available sessions and their project paths
"transcript.py not found"
- The slash command looks for the script in several locations. Either:
- Keep the repo cloned at
~/Projects/claude-session-transcript/ - Or set
TRANSCRIPT_SCRIPTto the full path
- Keep the repo cloned at
Transcript is missing recent messages
- The JSONL file is written to as the session progresses, but there may be a slight lag
- If you're exporting the current session, the most recent exchange might not be flushed yet
- For the most complete transcript, export after the session ends (or use the auto-export hook)
Permission denied on JSONL files
- Claude Code's JSONL files are owner-only (
600). The script must run as the same user who ran the session.
Large JSONL files are slow to convert
- Full mode on a long session can produce very large transcripts. Use compact or detailed mode for day-to-day use.
- The converter is single-pass and streams — memory usage is proportional to the number of tool results, not the total file size.
All tested configurations for terminal scrollback capture:
| Config | Env Vars | Terminal Scrollback | tmux Capture | Flicker | Notes |
|---|---|---|---|---|---|
| A | DISABLE_ALTERNATE_SCREEN=1 |
Yes (capped ~300 lines) | Yes | Some | Best for short sessions |
| B | NO_FLICKER=1 |
No (alternate screen) | Visible screen only | None | Best rendering, no capture |
| C | NO_FLICKER=1 + DISABLE_MOUSE=1 |
No | Visible screen only | None | B + keyboard scrolling |
| D | NO_FLICKER=1 + DISABLE_ALTERNATE_SCREEN=1 |
Varies | Varies | Varies | Flags may conflict |
| E | All three | Varies | Varies | Varies | Not recommended |
| F | None (vanilla) | Default behavior | Default | Default | Baseline |
Conclusion: No configuration gives both flicker-free rendering AND full terminal capture. The JSONL converter works regardless of which env vars you use.
MIT
This project is not affiliated with, endorsed by, or sponsored by Anthropic. "Claude" and "Claude Code" are trademarks of Anthropic. This tool is an independent, community-built utility that works with Claude Code's local data files.
Created by George Samuelson (@georgesamuelson)