A Claude Code plugin that automatically saves readable Markdown transcripts of your sessions before context compaction.
When Claude Code compacts your conversation context (either automatically or via /compact), this plugin intercepts the PreCompact event and converts the full JSONL transcript into a clean, human-readable Markdown file.
Output includes:
- Timestamped User / Claude message sections
- Git branch change markers
- Tool calls with syntax-highlighted commands
- Tool results in collapsible
<details>blocks - Thinking blocks in collapsible sections
- System noise filtered out
/plugin marketplace add IDisposable/claude-transcript-plugin
/plugin install transcript-saver@transcript-saver/compact
Transcripts by default are in ~/.claude/transcripts/ or C:\User\you\.claude\templates\
Add this repository as a plugin marketplace, then install:
/plugin marketplace add IDisposable/claude-transcript-plugin
/plugin install transcript-saver@transcript-saverOr install directly from GitHub:
claude plugin install transcript-saver@transcript-saver --source github:IDisposable/claude-transcript-pluginDownload the pre-built binary for your platform from Releases and place it in the bin/ directory:
bin/save-transcript-<os>-<arch>[.exe]Supported platforms: linux, darwin, windows × amd64, arm64.
Or build from source (requires Go 1.26.1+):
go build -o bin/save-transcript ./cmd/save-transcript/claude --plugin-dir /path/to/claude-transcript-pluginAll settings follow the same precedence order:
CLI flag > environment variable > config file > default
There are two ways to configure the plugin: config files (recommended) and environment variables.
Create a transcript-saver.json file to set defaults. The plugin checks two locations and merges them (project overrides user):
| Location | Scope |
|---|---|
<project>/.claude/transcript-saver.json |
Per-project settings |
~/.claude/transcript-saver.json |
User-wide defaults |
{
"template": "brief",
"output_dir": "~/project-transcripts",
"tools_dir": "~/.claude/transcript-tools"
}All fields are optional. Only the fields you specify are applied; the rest use defaults.
# In your project root
mkdir -p .claude
echo '{"template": "brief", "output_dir": "./transcripts"}' > .claude/transcript-saver.jsonecho '{"template": "default", "tools_dir": "~/.claude/transcript-tools"}' > ~/.claude/transcript-saver.jsonProject settings override user settings on a per-field basis. If your user config sets template to "default" but a project config sets it to "brief", that project uses "brief" while other projects use "default".
Settings can also be configured via environment variables, either in your shell or in Claude Code's settings:
| Variable | Purpose |
|---|---|
TRANSCRIPT_TEMPLATE |
Template name or path to custom .tmpl file |
TRANSCRIPT_OUTPUT_DIR |
Output directory for transcript files |
TRANSCRIPT_TOOLS_DIR |
Directory of external tool template overrides |
export TRANSCRIPT_TEMPLATE=brief
export TRANSCRIPT_OUTPUT_DIR=~/my-transcripts
export TRANSCRIPT_TOOLS_DIR=~/my-tool-templatesYou can also set these in Claude Code's own settings file for per-project or global use:
// ~/.claude/settings.json (global) or <project>/.claude/settings.json
{
"env": {
"TRANSCRIPT_TEMPLATE": "brief"
}
}All settings can be overridden on the command line (highest precedence):
echo '...' | save-transcript --template brief --output-dir /tmp/out --tools-dir ~/toolsOr via the hook command in hooks/hooks.json:
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/run --template brief"| Setting | CLI flag | Env var | Config key | Default |
|---|---|---|---|---|
| Template | --template |
TRANSCRIPT_TEMPLATE |
template |
"default" |
| Output directory | --output-dir |
TRANSCRIPT_OUTPUT_DIR |
output_dir |
~/.claude/transcripts/ |
| Tools directory | --tools-dir |
TRANSCRIPT_TOOLS_DIR |
tools_dir |
~/.claude/transcript-tools/ (if exists) |
Templates use Go text/template syntax and control how the Markdown output is formatted. The built-in templates live in internal/transcript/templates/ and are embedded into the binary at compile time.
A template file is a single .tmpl file containing multiple named blocks defined with {{define "name"}}...{{end}}. Each block renders one kind of element:
| Block | Data fields | Purpose |
|---|---|---|
header |
.ProjectName .SessionStart .Cwd .TranscriptPath |
Document header |
branch |
.Branch |
Git branch change marker |
user |
.Timestamp .Content |
User message |
tool_response |
.Timestamp .Content |
Tool result returned to Claude |
assistant |
.Timestamp .Content |
Claude's response |
tool_result |
.Content |
Collapsible tool output detail |
thinking |
.Preview |
Collapsible thinking block |
tool_* |
.Name .Input |
Tool-specific formatting (see below) |
tool_default |
.Name .Input |
Fallback for unknown tools |
Templates are loaded in three layers, where later definitions override earlier ones:
- Built-in tool templates (
internal/transcript/templates/tools/*.tmpl) — one file per tool, shipped with the binary - Base template (
internal/transcript/templates/<name>.tmpl) — structural blocks (header, user, assistant, etc.) - External tool templates (
~/.claude/transcript-tools/*.tmpl) — user or third-party overrides
This means you can override any single tool's rendering without touching the rest of the template.
A custom base template only needs to define the structural blocks — tool rendering is inherited from the built-in tool templates unless you explicitly override them.
-
Copy the default template as a starting point:
cp internal/transcript/templates/default.tmpl ~/my-template.tmpl -
Edit the blocks you want to change. For example, to make user messages more prominent:
{{define "user" -}} --- # USER {{.Timestamp}} {{.Content}} {{end}} -
Use it:
export TRANSCRIPT_TEMPLATE=~/my-template.tmpl
To hide a tool's output entirely, define an empty template block for it. Any tool template that renders to empty/whitespace is silently skipped.
In a custom base template:
{{define "tool_write" -}}{{- end}}
{{define "tool_edit" -}}{{- end}}Or as an external override file (e.g. ~/.claude/transcript-tools/write.tmpl):
{{define "tool_write" -}}{{- end}}This works for any tool — built-in or third-party. For example, a brief.tmpl variant that omits generated code, file writes, and thinking blocks might define:
{{define "tool_write" -}}{{- end}}
{{define "tool_edit" -}}{{- end}}
{{define "thinking" -}}{{- end}}All other tools inherit their default rendering.
There may be template rendering that is difficult or non-obvious to express in markdown syntax. You can use these functions to represent the specified outputs.
| Function | Output | Purpose |
|---|---|---|
{{mdBr}} |
(two spaces) |
Markdown line break — use instead of invisible trailing spaces |
{{ucEllip}} |
… (Unicode ellipsis) |
Unicode ellipsis - use to easily indicate truncations with … instead of ... |
To ship a new variant (e.g. brief) with the plugin:
- Create
internal/transcript/templates/brief.tmplwith the structural blocks. - Rebuild. The
//go:embeddirective picks it up automatically. - Users select it with
TRANSCRIPT_TEMPLATE=brief.
The variant inherits all built-in tool templates. It can override specific tools by defining them in its own file.
When Claude Code adds a new tool, the converter automatically handles it via the tool_default template, which renders **Tool: ToolName**. To give a new tool richer formatting, you only need to add a template file — no Go code changes required.
-
The converter lowercases the tool name and looks for a template named
tool_<name>. -
If found, it renders that template. If not, it falls back to
tool_default. -
All tool templates receive the same data structure:
Field Type Description .Namestring Tool name as-is (e.g. "Bash","WebSearch").Inputmap All input fields from the tool call, keyed by their JSON field names
Create a file in internal/transcript/templates/tools/ named <toolname>.tmpl (lowercase) containing a single {{define}} block:
{{define "tool_deploy" -}}
**Tool: Deploy** {{.Input.service}} → `{{.Input.target}}`
{{- end}}Rebuild and it's picked up automatically.
Drop a .tmpl file into the tools directory. The default location is ~/.claude/transcript-tools/, overridable with --tools-dir or TRANSCRIPT_TOOLS_DIR.
# Create the tools directory
mkdir -p ~/.claude/transcript-tools
# Add a template for a custom MCP tool
cat > ~/.claude/transcript-tools/deploy.tmpl << 'EOF'
{{define "tool_deploy" -}}
**Tool: Deploy** {{.Input.service}} → `{{.Input.target}}`
{{- end}}
EOFExternal templates override built-in ones with the same name, so you can also use this to customize how existing tools (like Bash or Read) are rendered.
If you're building a Claude Code tool (MCP server, plugin, etc.) and want to provide a default transcript rendering, ship a .tmpl file alongside your tool with the following convention:
- File name:
<toolname>.tmpl(lowercase, matching your tool's name) - Contents: A single
{{define "tool_<toolname>"}}block - Data available:
{{.Name}}(tool name) and{{.Input.<field>}}(all input fields from the tool call) - Install instructions: Tell users to copy the file to
~/.claude/transcript-tools/
Input fields are accessed via {{.Input.field_name}} using the exact JSON key names from the tool call. Common patterns:
{{.Input.file_path}} Access a string field
{{with .Input.description}} Conditional: only render if field exists
— {{.}}
{{end}}
{{with .Input.path}} Provide a default value
{{.}}
{{else}}
.
{{end}}
{{range .Input.items}} Iterate over an array field
{{index . "key"}}
{{end}}Some tools produce very large input values. To keep transcripts readable, the converter truncates specific fields before passing them to templates. The rules are defined in parser.go:
var truncationRules = map[string]map[string]int{
"Write": {"content": 500},
"Edit": {"old_string": 200, "new_string": 200},
}To add truncation for a new tool, add an entry to this map.
| Component | Purpose |
|---|---|
hooks/hooks.json |
Registers a PreCompact hook |
scripts/run |
Unix shim — detects OS/arch, runs correct binary |
scripts/run.cmd |
Windows shim for native Windows |
cmd/save-transcript/ |
Go entry point — reads hook stdin, writes Markdown |
internal/transcript/ |
JSONL parsing, classification, template rendering |
internal/transcript/templates/ |
Embedded .tmpl template files |
skills/save-transcript/ |
Manual /transcript-saver:save-transcript skill |
The hook receives the session's transcript_path from Claude Code via stdin JSON, reads the JSONL file, and writes a formatted Markdown file. It exits cleanly on any error (including panics) so it never blocks compaction.
See CONTRIBUTING.md for build instructions, plugin structure, CI details, and development conventions.
# Session Transcript: my-project
**Started:** 2026-03-05T15:21:47.027Z
**Project:** `/home/user/my-project`
---
> *Branch: `main`*
## User _15:21:47_
Please review the authentication module.
---
## Claude _15:21:50_
I'll start by reading the auth files.
**Tool: Read** `src/auth/handler.ts`
---
### Tool Response _15:21:51_
<details><summary>Tool Result</summary>
...
</details>MIT
