---
title: "Export Claude Code Session to Markdown: Recover Plans"
slug: "export-claude-code-session-to-markdown"
published: "2026-06-19"
updated: "2026-06-22"
validated: "2026-06-22"
categories:
  - "AI"
tags:
  - "export Claude Code session"
  - "Claude Code .jsonl"
  - "export session to Markdown"
  - "Python JSONL exporter"
  - "grep .jsonl"
  - "resume Claude session"
  - "session transcript export"
  - "built-in /export command"
  - "parse Claude Code transcripts"
  - "searchable session notes"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "claude-code@latest"
  - "python>=3.8"
  - "jq>=1.6"
  - "bash (POSIX)"
status: "stable"
llm-purpose: "Export Claude Code session to Markdown to recover lost plans. Locate .jsonl transcripts, run the Python export or built-in /export, and get searchable…"
llm-prereqs:
  - "Access to Claude Code"
  - "Access to Python 3"
  - "Access to grep"
  - "Access to sed"
  - "Access to less"
llm-outputs:
  - "Completed outcome: Export Claude Code session to Markdown to recover lost plans. Locate .jsonl transcripts, run the Python export or built-in /export, and get searchable…"
---

**Summary Triples**
- (Claude Code, stores session transcripts at, ~/.claude/projects/<encoded-project-path>/<session-id>.jsonl)
- (Project path encoding, is, replace '/' with '-' and prefix with '-' (e.g. /home/matija/etsy-slo -> -home-matija-etsy-slo))
- (Each .jsonl line, represents, one JSON object for a message, tool call, or tool result)
- (Find sessions by name, use, grep -RIn '<plan-or-agent-name>' ~/.claude/projects)
- (Inspect raw JSONL, use, head/less and jq to examine keys: head -n1 file.jsonl | jq .)
- (Export to Markdown, options, run a Python exporter script or use the built-in /export command in Claude Code)
- (Python exporter workflow, steps, read file line-by-line, json.loads each line, extract role+text/timestamp, render into Markdown, write file.md)
- (Search inside a JSONL transcript, use, while-read + jq or grep per-line JSON parsing (e.g. while read l; do echo "$l" | jq -r '.text // .content'; done < file.jsonl))
- (If resume is not available, recover plan by, exporting readable Markdown and pasting plan into a new session)

### {GOAL}
Export Claude Code session to Markdown to recover lost plans. Locate .jsonl transcripts, run the Python export or built-in /export, and get searchable…

### {PREREQS}
- Access to Claude Code
- Access to Python 3
- Access to grep
- Access to sed
- Access to less

### {STEPS}
1. Locate Claude Code project folder
2. Grep for agent or plan name
3. Set FILE variable and verify file
4. View raw JSONL for quick inspection
5. Run Python exporter to Markdown
6. Search and filter the exported Markdown
7. Resume or use built-in export if needed

<!-- llm:goal="Export Claude Code session to Markdown to recover lost plans. Locate .jsonl transcripts, run the Python export or built-in /export, and get searchable…" -->
<!-- llm:prereq="Access to Claude Code" -->
<!-- llm:prereq="Access to Python 3" -->
<!-- llm:prereq="Access to grep" -->
<!-- llm:prereq="Access to sed" -->
<!-- llm:prereq="Access to less" -->
<!-- llm:output="Completed outcome: Export Claude Code session to Markdown to recover lost plans. Locate .jsonl transcripts, run the Python export or built-in /export, and get searchable…" -->

# Export Claude Code Session to Markdown: Recover Plans
> Export Claude Code session to Markdown to recover lost plans. Locate .jsonl transcripts, run the Python export or built-in /export, and get searchable…
Matija Žiberna · 2026-06-19

If you've lost track of a Claude Code session and need to recover a plan, a todo list, or a specific decision buried in the transcript, you can find it by searching the local `.jsonl` session files Claude Code stores on disk, then export that file into a readable Markdown document. This guide walks through locating the right session by agent or plan name, inspecting the raw transcript, and converting it into something you can actually read and search.

I ran into this last week while working on a multi-step product onboarding flow for an Etsy-style storefront. I'd planned out a whole approach in a Claude Code session days earlier, moved on to other work, and then needed to go back and check exactly what we'd agreed on. The session was still on disk, just buried inside a raw JSONL file with every tool call and tool result mixed in with the actual plan. Here's the workflow I used to dig it out.

## Where Claude Code Stores Session Transcripts

Claude Code keeps local session transcripts at:

```bash
~/.claude/projects/<encoded-project-path>/<session-id>.jsonl
```

The project path gets encoded by replacing slashes with dashes. So a project at:

```bash
/home/matija/etsy-slo
```

ends up stored under:

```bash
~/.claude/projects/-home-matija-etsy-slo/
```

and an individual session file looks like this:

```bash
~/.claude/projects/-home-matija-etsy-slo/d54cb123-ae10-48f3-9e8f-bb805061cc88.jsonl
```

Each line in that file is a JSON object representing one message, tool call, or tool result from the session.

## Searching for the Right Session by Name

If you remember the agent name or plan name but not the session ID, grep across the whole Claude folder:

```bash
grep -RIn 'multi-step-product-onboarding' ~/.claude/projects ~/.claude/tasks ~/.claude/plans 2>/dev/null
```

When you know which project the session belongs to, narrow the search to that project folder to cut down on noise:

```bash
grep -RIn 'multi-step-product-onboarding' ~/.claude/projects/-home-matija-etsy-slo ~/.claude/tasks ~/.claude/plans 2>/dev/null
```

The match will point you to a `.jsonl` file. That's your session.

## Setting Up the Session File for Inspection

Once you've found the file, store its path in a variable so the rest of the commands stay short:

```bash
FILE="/home/matija/.claude/projects/-home-matija-etsy-slo/d54cb123-ae10-48f3-9e8f-bb805061cc88.jsonl"
```

Confirm it exists before doing anything else:

```bash
ls -lh "$FILE"
```

## Viewing the Raw Transcript

You can page through the file directly:

```bash
less "$FILE"
```

Add line numbers if you'll need to reference specific spots later:

```bash
nl -ba "$FILE" | less
```

Or jump straight to a line range you already suspect is relevant:

```bash
nl -ba "$FILE" | sed -n '80,220p'
```

This works for a quick look, but raw JSONL is awkward to read for anything longer than a few lines, since every message is wrapped in tool call metadata and JSON escaping. For anything beyond a quick peek, export it.

## Exporting the Session to Readable Markdown

This Python script reads the JSONL file line by line, pulls out the role and content of each message, and prints it as Markdown with clear `## USER` / `## ASSISTANT` headers. It also surfaces tool calls and tool results so you can see what Claude actually ran, not just what it said.

```python
# File: export_session.py
import json

file = "/home/matija/.claude/projects/-home-matija-etsy-slo/d54cb123-ae10-48f3-9e8f-bb805061cc88.jsonl"

with open(file, "r", encoding="utf-8") as f:
    for line in f:
        try:
            obj = json.loads(line)
        except Exception:
            continue

        msg = obj.get("message") or {}
        role = msg.get("role")
        content = msg.get("content")

        if not role or not content:
            continue

        print(f"\n\n## {role.upper()}\n")

        if isinstance(content, str):
            print(content)

        elif isinstance(content, list):
            for item in content:
                if not isinstance(item, dict):
                    continue

                if item.get("type") == "text":
                    print(item.get("text", ""))

                elif item.get("type") == "tool_use":
                    print("\n```txt")
                    print("TOOL:", item.get("name"))
                    print(item.get("input"))
                    print("```")

                elif item.get("type") == "tool_result":
                    print("\n```txt")
                    print("TOOL RESULT:")
                    result = item.get("content")
                    if isinstance(result, str):
                        print(result[:4000])
                    else:
                        print(str(result)[:4000])
                    print("```")
```

Each tool result gets truncated to 4000 characters. That keeps massive file dumps or long command outputs from blowing up the export, while still leaving enough context to understand what happened at each step.

Run it and redirect the output into a Markdown file:

```bash
python3 export_session.py > /home/matija/etsy-slo/multi-step-product-onboarding-session.md
```

## Reading and Searching the Exported File

Open the Markdown export the same way you'd read any long document:

```bash
less /home/matija/etsy-slo/multi-step-product-onboarding-session.md
```

Inside `less`, search forward with `/` followed by your term:

```txt
/onboarding
```

Press `n` to jump to the next match. This is usually faster than scrolling once you know roughly what you're looking for.

## Filtering Down to the Relevant Sections

A full session export can run thousands of lines once tool calls and results are included. To pull out just the parts related to your plan, grep the exported Markdown for the keywords that matter:

```bash
grep -niE 'multi|step|product|onboarding|plan|todo|task|approach|implement' /home/matija/etsy-slo/multi-step-product-onboarding-session.md | head -100
```

Save the matches to their own file if you want something you can hand off or reference later without scrolling through the full export:

```bash
grep -niE 'multi|step|product|onboarding|plan|todo|task|approach|implement' /home/matija/etsy-slo/multi-step-product-onboarding-session.md > /home/matija/etsy-slo/multi-step-product-onboarding-matches.md
```

Then open the smaller file:

```bash
cat /home/matija/etsy-slo/multi-step-product-onboarding-matches.md
```

## Resuming the Original Session

If you want to pick the conversation back up instead of just reading it, Claude Code can resume a session directly using its ID. Run the resume command from the same project directory the session was originally created in, since the working directory affects what Claude can see and access:

```bash
cd /home/matija/etsy-slo
claude --resume d54cb123-ae10-48f3-9e8f-bb805061cc88
```

## Using the Built-In Export Command

Claude Code also ships with a built-in export command you can run from inside an active session:

```txt
/export session-name.md
```

This produces a readable Markdown export directly, without writing or running the Python script above. It's the quickest path when you're exporting the session you're currently in. The manual JSONL parsing approach earns its place when you need to recover a session you're no longer inside of, want full control over formatting, or need to script the export across many sessions at once.

## Quick Reference Script

Here's the whole workflow as one block, ready to adapt for a different session:

```bash
# File: export_and_view.sh
FILE="/home/matija/.claude/projects/-home-matija-etsy-slo/d54cb123-ae10-48f3-9e8f-bb805061cc88.jsonl"
OUT="/home/matija/etsy-slo/multi-step-product-onboarding-session.md"

python3 - <<'PY' > "$OUT"
import json

file = "/home/matija/.claude/projects/-home-matija-etsy-slo/d54cb123-ae10-48f3-9e8f-bb805061cc88.jsonl"

with open(file, "r", encoding="utf-8") as f:
    for line in f:
        try:
            obj = json.loads(line)
        except Exception:
            continue

        msg = obj.get("message") or {}
        role = msg.get("role")
        content = msg.get("content")

        if not role or not content:
            continue

        print(f"\n\n## {role.upper()}\n")

        if isinstance(content, str):
            print(content)
        elif isinstance(content, list):
            for item in content:
                if isinstance(item, dict) and item.get("type") == "text":
                    print(item.get("text", ""))
PY

less "$OUT"
```

Swap in your own `FILE` path and `OUT` destination, and this gives you a one-shot export-and-read command for any session.

## FAQ

**Where exactly does Claude Code store session files on disk?**
Under `~/.claude/projects/<encoded-project-path>/<session-id>.jsonl`, where the encoded project path replaces every slash in your project's absolute path with a dash.

**How do I find a session if I only remember a rough description, not the exact agent name?**
Grep with a broader set of keywords across `~/.claude/projects`, `~/.claude/tasks`, and `~/.claude/plans`. Partial matches on distinctive words from the plan usually surface the right file.

**Why does the export script truncate tool results to 4000 characters?**
Long tool outputs, like full file dumps or verbose command logs, can make the exported Markdown unwieldy. Truncating keeps the file readable while preserving enough of each result to follow the session's logic.

**Can I export a session I'm not currently inside of?**
Yes. The Python script works against any `.jsonl` file on disk regardless of whether that session is active. The built-in `/export` command only works for the session you're currently running.

**Does resuming a session require being in the original project folder?**
Yes. Claude Code's resume command ties the session to the working directory it was created in, so running `claude --resume` from a different folder can change what Claude sees and is able to do.

## Wrapping Up

Recovering a Claude Code plan you thought you'd lost comes down to three steps: locate the right `.jsonl` file by grepping for a name you remember, convert it into Markdown so it's actually readable, and search that export for the specific decision or task you need. Once you've done this a couple of times, the whole loop takes a couple of minutes, and you stop worrying about losing context between sessions.

Let me know in the comments if you have questions, and subscribe for more practical development guides.

Thanks,
Matija

## LLM Response Snippet
```json
{
  "goal": "Export Claude Code session to Markdown to recover lost plans. Locate .jsonl transcripts, run the Python export or built-in /export, and get searchable…",
  "responses": [
    {
      "question": "How do I find the .jsonl session file for a specific project or plan?",
      "answer": "1) List recent session files: ls -lt ~/.claude/projects/*/*.jsonl | head 2) If you know the project path, encode it by replacing '/' with '-' and prefix with '-' (example: /home/matija/etsy-slo -> -home-matija-etsy-slo). Then look for files under ~/.claude/projects/-home-matija-etsy-slo/ 3) If you only remember an agent or plan name, grep the whole Claude folder: grep -RIn 'your-plan-or-agent-name' ~/.claude/projects 4) When you find a candidate filename, inspect the first line to confirm structure: head -n1 ~/.claude/projects/<encoded>/<session-id>.jsonl | jq ."
    },
    {
      "question": "How can I inspect raw transcript contents quickly?",
      "answer": "1) View the file as line-delimited JSON: less ~/.claude/projects/<encoded>/<session-id>.jsonl 2) Inspect the first line and print keys: head -n1 file.jsonl | jq 'keys' 3) Dump a readable sample: head -n20 file.jsonl | jq -r '.text // .content // .message // tostring' 4) To view with line numbers: nl -ba file.jsonl | less"
    },
    {
      "question": "How do I export a .jsonl session to Markdown with a small Python script?",
      "answer": "1) Inspect one JSON line to find the message fields (use jq). 2) Create a script (export_jsonl_to_md.py) that reads each line, json.loads it, extracts role/author + text and timestamp, and writes formatted Markdown. Minimal example (invoke as: python export_jsonl_to_md.py session.jsonl > session.md): python - <<'PY' import json,sys f=sys.argv[1] with open(f) as fh: out=[] for ln in fh: obj=json.loads(ln) # adjust keys below to match your file (common keys: 'author','role','text','content','message') role=obj.get('author') or obj.get('role') or obj.get('type') or 'unknown' text=(obj.get('text') or obj.get('content') or obj.get('message') or '') ts=obj.get('created_at') or obj.get('timestamp') or '' if text: header=f\"### {role} {('@'+ts) if ts else ''}\\n\" out.append(header+text.strip()) print('\\n\\n'.join(out)) PY 3) Redirect output to a .md file: python export_jsonl_to_md.py session.jsonl > session.md 4) Open session.md in your editor to review and clean any tool-result noise."
    },
    {
      "question": "What's a command-line way to extract all user and assistant text using jq?",
      "answer": "Use per-line parsing because .jsonl is line-delimited. Example loop that prints likely text fields: while read -r l; do echo \"$l\" | jq -r '.text // .content // .message // empty'; done < session.jsonl > session_text.txt This captures whichever of the common keys exist. If your file uses nested structures, inspect one record with: head -n1 session.jsonl | jq . and adapt the jq path."
    },
    {
      "question": "How do I use Claude Code's built-in /export to get Markdown?",
      "answer": "1) Open the session in the Claude Code app/IDE. 2) In the session conversation input, type the built-in export command (e.g. /export or the Export action in the session menu). Options may include formats — choose Markdown if prompted. 3) Download the exported file or copy the exported Markdown into your editor. If the UI differs by version, look for an Export or Session > Export menu item. If /export is not available, use the Python exporter above to produce a Markdown file."
    },
    {
      "question": "How can I recover a plan and continue the session if resume isn't supported?",
      "answer": "1) Export the readable Markdown (using /export or the Python exporter). 2) Create a new Claude Code session and paste the recovered plan/steps into the new session as context (you can add a short note like 'Recovered from session <session-id>'). 3) Optionally prune tool-result noise from the Markdown before pasting so the model focuses on the plan. 4) Save the new session and continue."
    },
    {
      "question": "How do I search for a phrase across all Claude sessions quickly?",
      "answer": "Use grep recursively over the projects folder: grep -RIn --exclude='*.lock' --exclude-dir='.git' 'search-phrase' ~/.claude/projects Add -n to show line numbers. Combine with head or xargs to preview matched files: grep -RIl 'search-phrase' ~/.claude/projects | xargs -r -n1 head -n5"
    }
  ]
}
```