← All guidesWorkflow

What Claude Code knows about its own sessions

If you run several Claude Code sessions at once, sooner or later you want a script that answers "what is running right now, and where?". Hooks tell you when something happens, but they cannot tell you what was already going before your script started.

Claude Code writes the answer to disk, and almost nobody knows it is there.

The registry

Look in ~/.claude/sessions/. There is one JSON file per session, named after the process id:

$ ls ~/.claude/sessions/
1301.json  1324.json  3940.json  ...

Each one is a compact snapshot of that session:

{
  "pid": 1324,
  "sessionId": "2b96cc05-8c0a-4b86-bb4e-1cac777a8241",
  "cwd": "/home/you/work/api",
  "startedAt": 1788555799810,
  "procStart": "2277",
  "version": "2.1.261",
  "kind": "interactive",
  "entrypoint": "cli",
  "name": "API",
  "nameSource": "user",
  "updatedAt": 1788587245699,
  "status": "busy",
  "statusUpdatedAt": 1788587245699
}

The fields that earn their keep:

FieldWhat you get
cwdThe directory the session runs in. This is the one that is genuinely hard to get any other way.
sessionIdThe same id hooks put in their payload, so you can join the two exactly.
nameThe session name, with nameSource telling you whether you set it or it was derived.
statusbusy, idle, waiting or shell, as of statusUpdatedAt.
pid / procStartThe process, and a token to tell it apart from a later process that reused the number.

The catch: nothing cleans it up

Read it naively and you will be badly wrong. The files are not removed when a session ends. On the machine this guide was written on:

40 files in ~/.claude/sessions/
 2 processes actually alive

Some of those files were four months old and still said "status": "busy". The registry is a last known state, not a list of what is running. A session that crashes, or that you close with the window, leaves its file behind saying whatever it happened to be doing at the time.

So the file answers "where was this session, and what was it called". It does not answer "is it alive". For that you need one more step.

Reading it correctly

Take the registry as your list of candidates, then keep only the ones whose process is still there. On macOS or Linux:

#!/usr/bin/env bash
# Live Claude Code sessions: cwd, name and status.
for f in ~/.claude/sessions/*.json; do
  pid=$(jq -r '.pid' "$f" 2>/dev/null) || continue
  kill -0 "$pid" 2>/dev/null || continue          # gone? skip it
  jq -r '[.pid, .status, .name // "-", .cwd] | @tsv' "$f"
done

On Windows, ask for the process list instead:

Get-ChildItem "$env:USERPROFILE\.claude\sessions\*.json" | ForEach-Object {
  $s = Get-Content $_ -Raw | ConvertFrom-Json
  if (Get-Process -Id $s.pid -ErrorAction SilentlyContinue) {
    "{0}`t{1}`t{2}`t{3}" -f $s.pid, $s.status, $s.name, $s.cwd
  }
}

That is enough for anything interactive. If you want to be strict about it, there is a second failure mode: process ids get reused, so a dead session's number can come back attached to something else entirely. procStart is the guard. On Linux it is the process start time in clock ticks, the same value as field 22 of /proc/<pid>/stat:

$ jq -r .procStart ~/.claude/sessions/1324.json
2277
$ awk '{print $22}' /proc/1324/stat
2277

Compare the two and a recycled pid stops looking live. In practice you also want to confirm the process really is a claude, which the same check gives you for free.

Why the cwd is the interesting part

Every other field you could get some other way. The working directory you mostly cannot.

On Linux and inside WSL it is a readlink /proc/<pid>/cwd. On macOS you can get there with lsof -a -p <pid> -d cwd -Fn. On Windows there is no supported way at all: the value lives in the process environment block, and reading another process's PEB means opening it with memory-read rights and walking undocumented structures, which is the sort of thing that makes security software take an interest in your program.

A JSON file the application wrote itself is a considerably better idea than any of that.

Treat it as a convenience, not an API

This is internal state, not a published interface. The file above came from version 2.1.261; the shape can change in any release, and nothing promises it will still be there next month.

That is fine for what it is good at. Read it best-effort, tolerate missing fields, and make sure your script still does something sensible when the directory is empty or the JSON has grown a key you have never seen. What you should not do is build something that stops working the day the format moves.

Knowing what is running, without a script

The reason to want any of this is usually the same: several sessions going at once, and no quick way to see which is which. A registry read gets you a list in a terminal.

Blooby answers it without the terminal. It gives every project its own animated mascot on your desktop, so a glance tells you which session is working, which one is waiting on you and which one has finished. It reads the same ground truth this guide describes, for the same reason: hooks tell you what just happened, and the process tree tells you what is actually there.

See it for yourself

download Get Blooby free