Skip to content

georgesamuelson/claude-session-transcript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-session-transcript

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.

Why This Exists

If you've tried to save a Claude Code session transcript, you've probably hit one of these walls:

The Scrollback Problem

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.

The Workarounds (and Why They're Not Enough)

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.

The Built-in /export Command

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.

The JSONL Solution

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.

What You Get

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.

Mode Comparison

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

Recommended Workflow: Generate Multiple, Use What You Need

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.md

This 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.

Token & Size Considerations

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.

Installation

Quick Setup (recommended)

git clone https://github.com/georgesamuelson/claude-session-transcript.git
cd claude-session-transcript
./install.sh

The installer walks you through:

  1. Which modes to auto-export (compact, detailed, full, all, or none)
  2. Where to save transcripts (per-project or centralized)
  3. Slash command — installs /transcript for use inside sessions
  4. 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.

Optional: Auto-Approve /transcript

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 with python3, not just the transcript converter.

Manual: Slash Command Only

Copy the skill into your Claude Code skills directory:

cp -r claude-session-transcript/skills/transcript ~/.claude/skills/claude-session-transcript

Then 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: transcript field in SKILL.md.

Manual: Auto-Export Hook Only

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 Stop hooks, add the new hook entry to your existing array. The async: true flag 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 dir

Standalone CLI (no Claude Code integration)

git 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 myapp

Finding Your Session Files

Claude 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 --list

Context Window Compaction

When 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

Requirements

  • Python 3.6+
  • No external dependencies (pure standard library)

The tmux Method (Preferred When It Works)

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.

Live Scrollback vs Transcript Export

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=1

This 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.

Dual Approach

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.

Customization

Slash Command Defaults

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.md to your preferred filename pattern

Auto-Export Configuration

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.py

Environment 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 locations

Output Filenames

The 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

Worktree Support

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 .git is 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 worktrees and is configurable via TRANSCRIPT_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.

Troubleshooting

"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 --list to 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_SCRIPT to the full path

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.

Environment Variable Reference

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.

License

MIT

Disclaimer

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.

Author

Created by George Samuelson (@georgesamuelson)

About

Full session transcripts from Claude Code — including content lost to terminal scrollback and context window compaction.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors