New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

claude-code-session-tracker

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

claude-code-session-tracker

See every Claude Code session on your machine in a local dashboard.

Source
npmnpm
Version
0.3.1
Version published
Weekly downloads
0
Maintainers
1
Weekly downloads
 
Created
Source

claude-code-session-tracker

CI npm node license

See every Claude Code session on your machine in a local dashboard — grouped by project, with live status, titles, and token usage.

npx claude-code-session-tracker

Or install it for good, if you would rather have it on your PATH:

brew install meyusufdemirci/tap/claude-code-session-tracker

Then open the printed http://127.0.0.1:3099.

Run Claude Code in four terminals and you lose track of which one is waiting on you, which is still working, and what you asked the one you abandoned yesterday. Claude Code already writes all of that to ~/.claude. This reads it — nothing else — and puts it on one page.

What you get

  • Active sessions, checked twice against the OS so a stale file or a recycled PID never shows up as running.
  • Recent sessions across every project, with Claude's own title, the first and last prompt, the model, and the branch — narrowed to today, yesterday, the last 3, 7 or 30 days, or a date range of your own, and ordered by recency or by token spend.
  • A detail panel per session: message and tool-call counts, token totals, elapsed and working time, subagent count, a copyable claude --resume <id>, and a button that shows the transcript in your file manager.
  • --json for scripting, and an HTTP API if you would rather build your own.
  • No dependencies, no install scripts, no network calls, no writes to your Claude directory.

Requirements

Node 20 or newer, and Claude Code having run at least once on this machine. macOS, Linux, and Windows are all covered by CI. Nothing else is needed — if ~/.claude does not exist yet, the page says so and fills in the moment your first session starts.

Run it with any package manager

npx      claude-code-session-tracker   # npm
pnpm dlx claude-code-session-tracker   # pnpm
yarn dlx claude-code-session-tracker   # yarn
bunx     claude-code-session-tracker   # bun

The package has no runtime dependencies and no install scripts, so every runner behaves the same.

Or install it

brew install meyusufdemirci/tap/claude-code-session-tracker
claude-code-session-tracker

The formula installs the same npm tarball the commands above download, so the two are the same program — it just lives on your PATH and updates with brew upgrade instead of being fetched each time. Homebrew's own node comes with it, which is why this is the one route that does not need Node already installed.

Elsewhere, npm i -g claude-code-session-tracker (or the pnpm add -g / bun add -g equivalent) does the same job.

Options

FlagDescription
-p, --port <number>Port to listen on, stepping forward up to 20 times if taken (default 3099)
--host <address>Address to bind (default 127.0.0.1 — see the warning below)
--no-openDo not open a browser
--jsonPrint the session list as JSON and exit
-n, --limit <number>How many sessions to list (default 50; running ones are always shown)
--claude-dir <path>Override the Claude data directory
-h, --helpShow usage
-v, --versionShow the version

Scripting

--json prints the same payload the page uses, then exits:

npx claude-code-session-tracker --json | jq '.sessions[] | .project.name'
{
  "sessions": [
    {
      "id": "279ed6ae-49fd-4234-a74e-145f5535341c",
      "source": "claude-code",
      "status": "busy",              // busy · waiting · idle · ended
      "project": { "name": "…", "path": "…", "slug": "…", "gitBranch": "main" },
      "title": "Disable dependabot", // Claude's own title, when it wrote one
      "firstPrompt": "…",
      "lastPrompt": "…",
      "model": "claude-sonnet-5",
      "version": "2.1.235",          // the Claude Code that wrote the session
      "startedAt": 1787142489923,
      "lastActiveAt": 1787142700231,
      "transcriptPath": "…/279ed6ae….jsonl",
      "sizeBytes": 136133,
      "live": { "pid": 4129, "kind": "interactive", "entrypoint": "cli" }
    }
  ],
  "sources": [ /* one entry per adapter, with whether it found its data */ ],
  "total": 794,
  "generatedAt": 1787142701002
}

Only running sessions carry live. Everything else is optional — a field that was not in the transcript is absent rather than null.

HTTP API

The server is the same one the page talks to, so anything the page can do you can do with curl:

RouteReturns
GET /api/sessions?limit=NThe list above. limit matches --limit, and running sessions are always included
GET /api/sessions?since=&until=The same list, narrowed to transcripts last written in that window. Epoch milliseconds; since is inclusive, until exclusive; either may be left off. Running sessions ignore it
GET /api/sessions?sort=recent (the default), tokens-desc, or tokens-asc. Ranks the finished sessions across the whole window, not just the page. An unknown value falls back to recent
GET /api/sessions/:idOne session with counts, tokens, models, activeMs, awaySummary, and notes
GET /api/healthok, the version, the Node it runs on, the resolved Claude directory, and per-source status
POST /api/sessions/:id/revealShows that transcript in your file manager. Requires a loopback Origin

Every route refuses a request whose Host is not loopback. reveal is the only one that acts rather than reports, so it is a POST, it checks Origin as well, and the path it opens comes from our own lookup — never from the request.

On the page

Click any row for the full read. Everything has a key:

KeyDoes
/Jump to the filter
↑ ↓Move between sessions, across both tables
Home EndFirst and last session
↵Open the selected session
EscClose the panel, or clear the filter

The list refreshes every 2 seconds and says so when the server goes away. The page takes the same limit from the query string, so ?limit=200 and --limit 200 show the same depth of history.

Range and Sort above the Recent table narrow it to a stretch of history and order it by recency or by token spend. Both go into the query string alongside the limit — ?range=7d&sort=tokens-desc, or ?range=custom&from=2026-08-01&to=2026-08-14 — so a reload comes back to the same view, and a bookmark keeps it. Ranges are whole local days, so "today" means since midnight rather than the last 24 hours. Reset appears beside them once either is off its default and puts both back; it leaves the text filter and how far you have paged alone. Neither control touches the Active table: a running session is shown whatever window is on screen.

The theme follows your OS by default; Auto / Light / Dark in the top right overrides it, and the choice is remembered.

What it reads

Everything comes from what Claude Code already writes to disk:

PathUsed for
~/.claude/sessions/<pid>.jsonRunning sessions and their live status
<session cwd>/.git/HEADThe branch a running session is on
~/.claude/projects/**/*.jsonlSession history — titles, prompts, models, branch
~/.claude.jsonPer-project rollup metrics (not read yet)

Set CLAUDE_CONFIG_DIR (or pass --claude-dir) if your Claude data lives somewhere other than ~/.claude. Some Claude Code versions accept a comma-separated list there; the first entry wins.

Privacy

The tool never writes to the Claude directory, binds to loopback only, rejects requests that are not addressed to a loopback host, and makes no outbound network calls of any kind. There is no telemetry and no update check.

Transcripts hold your prompts, your paths, and sometimes your secrets. That is why the default bind is 127.0.0.1 and why every request has to be addressed to a loopback host — a page on any website can otherwise point a browser at your localhost. Passing --host to something else drops that guard, so the CLI says so, loudly, before it starts.

For a machine you are SSH'd into, forward the port instead of opening the bind:

ssh -L 3099:127.0.0.1:3099 you@the-machine

Troubleshooting

Nothing is listed. Check curl -s 127.0.0.1:3099/api/health — it prints the directory that was searched. If that is not where your transcripts are, set CLAUDE_CONFIG_DIR or pass --claude-dir.

A session I just started is missing. The list refreshes every 2 seconds and a session appears once Claude Code has written its first record.

A session shows as idle while it is clearly working. Status comes from what Claude Code itself records in ~/.claude/sessions/. If that file is stale, the row is honest about the file rather than guessing.

The port is taken. It steps forward automatically, up to 20 times; the address it actually bound is the one printed. --port picks a different start.

An old session has no title. Titles are Claude's own, and older transcripts predate them. The derived name and the first prompt stand in.

Programmatic use

import { createConfig, SessionRegistry } from 'claude-code-session-tracker/core';

const registry = new SessionRegistry(createConfig());
const { sessions } = await registry.list({ limit: 20 });
const detail = await registry.detail(sessions[0].id);

createConfig() takes { claudeDir, host, port } overrides. registry.detail() resolves to null for an unknown id rather than throwing.

Development

Requires Node 22.18+ to run from source, because dev and test load .ts files directly and let Node strip the types. The published package is compiled JavaScript and runs on Node 20+.

pnpm install
pnpm dev                       # run from source, watch mode
pnpm test                      # unit tests
pnpm test:watch                # re-run on change
pnpm test:coverage             # unit tests + a coverage report
pnpm build                     # tsc + copy web assets
pnpm typecheck                 # src and test
npm pack                       # -> claude-code-session-tracker-<version>.tgz
pnpm smoke ./claude-code-session-tracker-<version>.tgz pnpm

test/ mirrors src/ and runs on node --test with no runner, no config, and no dependency — the same rule the package itself follows. Fixtures are real files in a temp directory rather than a mocked fs, because what is worth testing here are properties of real files: a multi-byte character cut by a chunk boundary, a slug only the directory tree can disambiguate, a cache that turns on mtime. test/helpers/records.ts holds the transcript shapes in one place, so the day the .jsonl format changes, the failure is a named test rather than a silent wrong number.

pnpm smoke installs the packed tarball into a temporary directory with the package manager you name, then runs the installed binary against a fixture transcript. It is what CI runs — Node 20/22/24 × npm/pnpm/yarn/bun on Linux, plus npm on macOS and Windows — so a change that only works from source fails before it ships.

Everything that parses a transcript lives in src/sources/claude-code/. The Source interface in src/sources/source.ts is the seam a second tool (Codex, Cursor) would plug into; src/core/ knows nothing about Claude Code.

Releasing is a tag: npm version <patch|minor|major> then git push --follow-tags. The release workflow re-runs the checks, publishes with npm provenance, and then moves the Homebrew tap forward.

pnpm formula prints the Homebrew formula for a published version, rendered from the tarball on npm — Homebrew wants a sha256 of the exact file it will download and npm only advertises a sha512, so the tarball is fetched and hashed rather than described. Nothing .rb is committed here: the rendered formula lives in meyusufdemirci/homebrew-tap, pushed by .github/workflows/homebrew.yml once the version is on the registry and once brew install, brew test and brew audit --strict have all passed on a macOS runner. That workflow also runs on its own from the Actions tab, which is the repair path when a release reaches npm but not the tap — an npm publish cannot be taken back, so it must not require a second version to fix.

The push needs a HOMEBREW_TAP_TOKEN repository secret: a fine-grained PAT with Contents: read and write on the tap repository, and nothing else.

pnpm formula                   # the formula for this package.json's version
pnpm formula --version latest  # for whatever npm currently serves

Project plan and phase breakdown: PLAN.md.

How it stays fast

Feature-complete for v1 — phases 0 through 4 of PLAN.md.

Running sessions come from ~/.claude/sessions/*.json. Those files outlive the processes that write them, so each one is checked twice before it becomes an Active row: process.kill(pid, 0), then the recorded start time against the real one, which is what rules out a recycled PID.

Recent sessions come from the transcripts, which run to about a gigabyte on a working machine. Listing them reads no file contents at all — one readdir per project and a stat per file is enough to sort by recency. Only the sessions actually shown are opened, and only their first 16 KB and last 64 KB, which is where the title, the prompts, the model and the branch live. Results are memoised against each file's size and mtime, so an untouched transcript is never read twice. Listing all 794 transcripts on the development machine takes ~230 ms cold and ~75 ms warm.

Both the date range and the sort are settled from that same sweep where they can be. A range is: stat already knows when each file was last written, so narrowing one costs nothing and opens fewer files than not narrowing it. Ordering by tokens is not — the totals are inside the transcripts — so it reads the whole range before it can rank it, which is what makes "the ten biggest sessions this week" the ten biggest of all 227 rather than of the ten on screen. That read is ~1.4 s cold for all 871 transcripts on the development machine, and free once memoised.

Opening a session streams its transcript once, line by line, capping how much of any single line it holds — the largest record on the development machine is 9.4 MB, and memory should be a property of the reader, not of the biggest tool output in the session. A 37 MB transcript answers in 99 ms; the slowest of all 795 is 140 ms.

Malformed lines are counted and skipped, never thrown on: the .jsonl format is private and undocumented, and it will change under us. When it does, the detail panel reports what it could not read in notes instead of pretending.

Contributing

Issues and pull requests are welcome. pnpm typecheck and a passing pnpm smoke against a fresh npm pack are what CI will ask of a change; both run in well under a minute locally.

Since every number here was measured on one machine, a bug report that includes your GET /api/health output and the notes from an affected session is worth far more than a description.

License

MIT © Yusuf Demirci

Keywords

claude

FAQs

Package last updated on 21 Aug 2026

Related posts