Export a Claude Code session JSONL transcript into a readable Markdown file, including internal reasoning blocks when present.
This script is intentionally narrow in scope. It exports from one explicitly configured Claude Code project directory and will not search for projects based on the current working directory. It also does not export tool calls.
Claude Code stores local session history as JSONL files inside project-specific directories. This script converts a selected session file into Markdown with sections for:
- User messages
- Claude responses
- Claude internal reasoning / thinking blocks
The reasoning export is deliberate. This tool is intended for private review, debugging, research, and transcript analysis.
The script is designed to avoid accidental reads outside the configured project directory.
It only accepts .jsonl files that are direct children of the configured PROJECT_DIR.
It rejects:
- Files outside
PROJECT_DIR - Files inside subdirectories of
PROJECT_DIR - Non-
.jsonlfiles - Ambiguous filename matches
- Ambiguous content/name/title matches
Even if you pass a full file path as an argument, the file is accepted only if its resolved physical parent directory exactly matches the configured PROJECT_DIR.
The script requires:
- Bash
jq- Standard Unix utilities:
find,sort,sed,basename,dirname,xargs,ls,date,grep
The script assumes jq is installed.
Before running the script, edit the PROJECT_DIR constant near the top:
PROJECT_DIR="$HOME/.claude/projects/REPLACE_WITH_SPECIFIC_PROJECT_DIR"Set it to the specific Claude Code project directory containing the session .jsonl files you want to export.
Example:
PROJECT_DIR="$HOME/.claude/projects/-Users-jake-src-my-project"The output directory defaults to:
EXPORT_DIR="$HOME/claude_exports"You can change that constant if desired.
Make the script executable:
chmod +x export_claude_transcript.shRun it without arguments to export the newest direct .jsonl session file in PROJECT_DIR:
./export_claude_transcript.shRun it with a specific session filename:
./export_claude_transcript.sh 12345678-abcd-1234-abcd-1234567890ab.jsonlRun it with a session id without the .jsonl extension:
./export_claude_transcript.sh 12345678-abcd-1234-abcd-1234567890abRun it with a partial session id or partial filename:
./export_claude_transcript.sh 12345678Run it with a human-readable session name/title:
./export_claude_transcript.sh "Refactor auth flow"Session lookup is literal and case-insensitive for partial filename and content/name/title search. Characters such as spaces, brackets, punctuation, *, and ? are treated as ordinary text.
Partial matches are allowed only if they resolve to exactly one matching .jsonl file. If multiple files match, the script exits and lists the matching sessions.
When a session argument is provided, the script resolves it in this order:
- Full or relative path, accepted only if it is a direct
.jsonlchild ofPROJECT_DIR - Exact filename inside
PROJECT_DIR - Exact session id without
.jsonl - Literal partial filename match
- Literal content/name/title match
The content/name/title fallback searches only direct .jsonl children of PROJECT_DIR.
It first checks likely structured fields such as:
title
name
summary
sessionName
session_name
conversationTitle
conversation_title
message.title
message.name
message.summary
message.sessionName
message.session_name
message.conversationTitle
message.conversation_title
Then it falls back to a raw fixed-string search over each direct .jsonl file.
Exports are written to:
$HOME/claude_exportsThe generated filename uses this format:
export_YYYYMMDD_HHMMSS_NAME.md
If you provide a session argument, that argument is used in the final filename after sanitization.
Example:
./export_claude_transcript.sh "Refactor auth flow"May produce:
export_20260524_143012_Refactor_auth_flow.md
If you run the script without a session argument, the actual session id is used instead:
export_20260524_143012_12345678-abcd-1234-abcd-1234567890ab.md
The output filename is sanitized so spaces, slashes, punctuation, and unusual characters do not create unsafe or invalid filenames.
At completion, the script prints:
Export complete. Your transcript with reasoning traces is saved at:
/path/to/exported/file.md
The final output uses plain printf '%s\n' formatting to avoid Unicode or format-string issues in terminal output.
The Markdown file starts with front matter:
---
title: Claude Code Custom Transcript
date: ...
session_id: ...
source_file: ...
---Then it emits the conversation as Markdown sections.
User messages are written as:
### User
Message text...Claude messages are written as:
### Claude
#### Reasoning
Reasoning text is written as an indented code block.
This avoids breakage if the reasoning contains triple backticks.
Visible response text...Reasoning blocks are written as indented Markdown code blocks instead of fenced code blocks. This makes the output structurally robust even when the reasoning itself contains Markdown fences such as:
```text
example
```The script does not silently skip missing reasoning.
If an assistant message lacks a reasoning block, the export includes:
#### Reasoning
[No reasoning block found in this assistant message.]This makes missing reasoning explicit in the generated transcript.
The script intentionally omits:
- Tool calls
- Tool inputs
- Tool outputs
- Non-text content blocks
- Files outside the configured project directory
- Files inside subdirectories of the configured project directory
Only user text, assistant text, and assistant reasoning/thinking blocks are exported.
The script supports both top-level and nested message structures.
It checks for roles in either:
.roleor:
.message.roleIt checks for content in either:
.contentor:
.message.contentContent may be either:
"plain string content"or an array of typed content blocks:
[
{
"type": "text",
"text": "Visible message text"
},
{
"type": "thinking",
"text": "Reasoning block"
}
]Reasoning is extracted from content blocks with type:
thinking
reasoning
The script checks these fields inside reasoning blocks:
.text
.thinking
.contentThe script resolves PROJECT_DIR with:
pwd -PIt validates every selected session file by resolving the candidate file’s parent directory physically and comparing it to the resolved PROJECT_DIR.
A session file is accepted only if:
- Its resolved parent directory exactly equals the resolved
PROJECT_DIR - It is a regular file
- Its basename ends in
.jsonl - It is a direct child of
PROJECT_DIR
This prevents accidental reads from sibling directories, parent directories, symlink escapes, and project subdirectories.
The configured project directory is wrong or no longer exists.
Check the value of:
PROJECT_DIR="..."The configured project directory exists, but it does not contain any direct .jsonl files.
Confirm that you pointed PROJECT_DIR at the specific Claude Code project history directory, not the parent ~/.claude/projects directory.
Your session argument matched more than one filename.
Use a longer session id, a full .jsonl filename, or the human-readable session name if that is more precise.
Your session argument appeared in more than one session file.
Use a more specific name/title fragment or pass the exact session filename.
The supplied session argument did not match any direct .jsonl file by filename, session id, or content/name/title search.
Run the script without arguments to export the newest available session, or inspect the listed available sessions.
This script depends on the current structure of Claude Code local JSONL session files. Claude Code’s internal storage format is not guaranteed to remain stable.
If Claude Code changes its transcript schema, the script may need updates.
The script does not attempt to repair malformed JSONL. If a session file contains invalid JSON lines, jq may fail.
The script does not redact secrets. Review output before sharing.
The content/name/title search is intentionally broad. It may match ordinary transcript content, not only formal session titles. This is useful when schemas vary, but it can also produce ambiguous matches.
Use this script for private local exports.
Suggested flow:
./export_claude_transcript.sh "Refactor auth flow"
open "$HOME/claude_exports"Then review the Markdown file before moving, publishing, or sending it anywhere.
Use at your own risk. No warranty is provided.