DEV Community

Stanley
Stanley

Posted on Edited on

How to Recover a Lost Claude Code Session When --continue Fails

I ran claude --continue to pick up where I left off, and it opened the wrong session. Here's why that happens, how to find the session by hand, and the small resume command I now use instead.

Why --continue misses sessions

Claude Code saves each session as a .jsonl file under ~/.claude/projects/. Each project gets a subfolder named after the full path where you launched claude, with slashes and underscores turned into dashes:

~/Documents/github/my_app  →  ~/.claude/projects/-Users-yourname-Documents-github-my-app/
Enter fullscreen mode Exit fullscreen mode

--continue only opens the most recent session in your current folder. So it picks the wrong one, or finds nothing, if:

  • you started a newer session in the same folder since then,
  • the original session was launched from a different folder (a parent or a subfolder),
  • you renamed or moved the project. Old sessions stay in a folder named after the old path, or
  • the session cd'd into a subfolder partway through. Every message records its working directory (cwd), so the session's latest folder may no longer be the one you started in.

First, try the built-in picker

claude --resume
Enter fullscreen mode Exit fullscreen mode

With no ID, this lists the current folder's sessions. The search box matches session titles, not IDs, so type a topic ("login bug") rather than pasting an ID. If your session isn't there, it probably lives under a different folder. Read on.

Finding a session by hand

See which sessions were used most recently, across all projects

ls -lt ~/.claude/projects/*/*.jsonl | head -20
Enter fullscreen mode Exit fullscreen mode

Use */*.jsonl rather than ls -lt ~/.claude/projects/. A folder's timestamp only changes when a session file is created, not when you keep chatting in one, so sorting the folders is misleading.

Search for a word from the lost conversation

ls -lt $(grep -l "webCorsOriginList" ~/.claude/projects/*/*.jsonl)
Enter fullscreen mode Exit fullscreen mode

grep -l lists the files containing the word, and ls -lt sorts them newest first. Two gotchas:

  • Use a word from your conversation, like a filename, config key or error message. Don't use the example word above.
  • The session you're in right now matches anything you've typed or pasted into it. If the top hit is your current session, look at the next one.

If zsh says no matches found, that folder has no .jsonl files. That happened to me with a leftover folder from before I renamed my project.

Resume it from the matching folder

The filename (minus .jsonl) is the session ID. To see which folder a session last worked in:

grep -o '"cwd":"[^"]*"' <session-file>.jsonl | tail -1
Enter fullscreen mode Exit fullscreen mode

Then:

cd /path/to/that/folder
claude --resume aaa6d99e-356e-4148-982e-e6e7a3133939
Enter fullscreen mode Exit fullscreen mode

Automating it: a resume picker

After doing that a few times, I wrote two zsh functions that do all of the above. They show a numbered list of recent sessions with the date, the folder and the first message you typed, which is usually enough to recognize the conversation. Pick a number, and it cds to the right folder and runs claude --resume for you:

$ resume
 1) Sep 23 13:21  my_app          how do I add a password to the streamlit secrets?
    cd /Users/yourname/Documents/github/my_app
    claude --resume cf39dcc2-7aa3-497e-85f5-81366bd7ca7f
 2) Sep 23 13:12  nfl_data        given SRS rating, what about defensive EPA?
    cd /Users/yourname/Documents/github/nfl_data
    claude --resume f7f54e6c-0a52-4123-93f8-8459b3e28de9
 3) Sep 23 12:29  anki-skill      is there a way to connect to my anki through Vercel?
    cd /Users/yourname/Documents/claude_skills/anki-skill
    claude --resume aaa6d99e-356e-4148-982e-e6e7a3133939
Session # (Enter to cancel): 2
cd /Users/yourname/Documents/github/nfl_data && claude --resume f7f54e6c-...
Enter fullscreen mode Exit fullscreen mode

Each row also prints the full cd and claude --resume commands, so you can copy them into another tab.

  • resume shows your 15 most recent sessions from any project, and resume 30 shows 30.
  • resumehere shows only sessions from the current folder ($PWD) or its subfolders, and resumehere 30 shows 30.

Paste this into ~/.zshrc, then run source ~/.zshrc. It needs zsh and python3, and I've only tested it on macOS.

# Usage: resume          -> numbered list of your 15 most recent sessions,
#                           showing each one's folder and first message.
#                           Type a number, and it cd's there and resumes it.
#        resume 30       -> show 30 instead
#        resumehere      -> same, but only sessions from this folder ($PWD)
#                           or its subfolders
#        resumehere 30   -> show 30 instead
resume()     { _resume_pick "${1:-15}"; }
resumehere() { _resume_pick "${1:-15}" "$PWD"; }

_resume_pick() {
  local n=$1 here=$2 choice i d id dir snip
  local -a rows
  rows=("${(@f)$(python3 - "$n" "$here" <<'PY'
import glob, json, os, re, sys, time
n, here = int(sys.argv[1]), sys.argv[2]
files = sorted(glob.glob(os.path.expanduser("~/.claude/projects/*/*.jsonl")),
               key=os.path.getmtime, reverse=True)
shown = 0
for f in files:
    if shown >= n:
        break
    with open(f, errors="replace") as fh:
        text = fh.read()
    cwds = re.findall(r'"cwd":"([^"]*)"', text)
    cwd = cwds[-1] if cwds else ""
    if here and not any(c == here or c.startswith(here + "/") for c in set(cwds)):
        continue
    first = ""
    for line in text.splitlines():
        if '"type":"user"' not in line:
            continue
        try:
            o = json.loads(line)
        except ValueError:
            continue
        if o.get("type") != "user" or o.get("isMeta"):
            continue
        c = (o.get("message") or {}).get("content")
        if isinstance(c, list):
            c = " ".join(b.get("text", "") for b in c
                         if isinstance(b, dict) and b.get("type") == "text")
        if isinstance(c, str):
            t = " ".join(c.split())
            if t and not t.startswith("<"):
                first = t
                break
    when = time.strftime("%b %d %H:%M", time.localtime(os.path.getmtime(f)))
    print("\t".join([when, os.path.basename(f)[:-6], cwd, first or "(no message)"]))
    shown += 1
PY
)}")
  rows=(${rows:#})
  if (( ! $#rows )); then
    echo "No sessions found${here:+ for $here}"; return 1
  fi
  for i in {1..$#rows}; do
    IFS=$'\t' read -r d id dir snip <<< "$rows[i]"
    printf '%2d) %s  %-28s %s\n' $i "$d" "${dir:t}" "$snip"
    printf "    cd %s\n" "$dir"
    printf "    claude --resume %s\n" "$id"
  done
  read "choice?Session # (Enter to cancel): "
  [[ -z $choice ]] && return
  if [[ $choice != <-> ]] || (( choice < 1 || choice > $#rows )); then
    echo "No session #$choice"; return 1
  fi
  IFS=$'\t' read -r d id dir snip <<< "$rows[choice]"
  if [[ ! -d $dir ]]; then
    echo "Folder no longer exists: $dir"; return 1
  fi
  echo "cd $dir && claude --resume $id"
  cd "$dir" && claude --resume "$id"
}
Enter fullscreen mode Exit fullscreen mode

How it works: Python sorts the session files by last modified time. For each one it takes the last recorded cwd and the first real user message, skipping system and command messages that start with <. zsh prints the list, reads your choice, then runs cd and claude --resume. After you quit Claude, your shell stays in that folder, so run cd - to go back.

Looking up a session by ID: sessions

Sometimes you already have a session ID, from an old terminal tab, a note, or the resume list, and just need to know which folder to resume it from. The built-in picker can't help there, since its search box doesn't match IDs. This smaller function lists recent sessions, or looks one up by the first few characters of its ID:

$ sessions b235b
Sep 24 11:05  b235b276-c4e1-4cc4-b940-66aa2e9981e8  /Users/yourname/Documents/claude_skills/anki-skill
cd /Users/yourname/Documents/claude_skills/anki-skill && claude --resume b235b276-c4e1-4cc4-b940-66aa2e9981e8
Enter fullscreen mode Exit fullscreen mode
  • sessions shows your 10 most recent sessions (time, ID and last folder), and sessions 25 shows 25.
  • sessions b235b shows sessions whose ID starts with b235b. If exactly one matches, it also prints the cd and claude --resume command to copy.

A number is read as a count and anything else as an ID prefix, so if your prefix happens to be all digits, type one more character. Add it to ~/.zshrc alongside resume:

# Usage: sessions          -> 10 most recent Claude Code sessions
#        sessions 25       -> 25 most recent
#        sessions b235b    -> sessions whose ID starts with b235b
sessions() {
  local arg=${1:-10} f cwd
  local -a files
  if [[ $arg == <-> ]]; then
    files=(~/.claude/projects/*/*.jsonl(N.om))
    files=($files[1,$arg])
  else
    files=(~/.claude/projects/*/${arg}*.jsonl(N.om))
    if (( ! $#files )); then
      echo "No session ID starting with $arg"; return 1
    fi
  fi
  for f in $files; do
    cwd=$(grep -o '"cwd":"[^"]*"' "$f" | tail -1 | cut -d'"' -f4)
    printf '%s  %s  %s\n' "$(date -r "$f" '+%b %d %H:%M')" "${f:t:r}" "$cwd"
  done
  if [[ $arg != <-> ]] && (( $#files == 1 )); then
    echo "cd $cwd && claude --resume ${files[1]:t:r}"
  fi
}
Enter fullscreen mode Exit fullscreen mode

It's pure zsh with no Python. (N.om) lists the session files newest first without erroring when nothing matches, and ${f:t:r} turns the file path into the session ID.

A note on multiple tabs

It's fine to resume a session while a different session is open in another terminal tab. Just don't resume the same session in two tabs at once, since both would write to the same transcript. Also avoid having two sessions edit the same files at the same time. Only one tab should be using a session at a time, because Claude Code doesn't know that two sessions are open writing to the same .jsonl file.

TL;DR

  1. claude --resume and search by topic.
  2. Not there? Run resume (or resumehere) from the function above and pick by number.
  3. Have an ID but not the folder? Run sessions <first few characters of the ID>.
  4. No functions? Run ls -lt $(grep -l "some_word" ~/.claude/projects/*/*.jsonl). The top hit's filename is the ID, and its cwd is the folder to cd into before claude --resume <id>.

Top comments (0)