Back to Insights
21 Jun 20264 min readAI & LLMs

You're Running a Dozen Agents. Which One Is Stuck?

Once you're past one or two Claude Code sessions, you lose the thread. Five terminals, a few worktrees, an agent on the laptop and two on a server — and no single answer to "what's actually running right now, and is any of it stuck?" The IDE shows you the session you're looking at. There's no ps for the fleet.

There is, actually — you just have to read the right directory. Every session is a transcript under ~/.claude/projects/, and those transcripts carry enough metadata to reconstruct the whole picture. We wrote a 130-line tool, fleet-ps, that turns them into one screen.

What's on disk

Each project gets a directory under ~/.claude/projects/ (its name is your working dir with the slashes turned to dashes — a lossy slug). Inside are the session transcripts, one .jsonl per session. Two facts make a fleet view cheap:

  1. The file's mtime is the session's last activity. A stat per file — no parsing — tells you which sessions are live and which went quiet hours ago.
  2. The transcript records its own context. Early lines carry cwd, gitBranch, and the Claude Code version. So you don't have to decode the lossy slug — you read the real project path and branch straight out of the file.

That's the whole trick: stat everything (cheap), keep the recently-active ones, and read just the head (for cwd/branch) and the tail (for health) of those.

fleet-ps

$ fleet-ps
fleet-ps — 18 session(s) (active < 6h)

   AGO  HEALTH  BRANCH                 PROJECT
    0s  ✓ ok    research/8808-recover… …/ceo-workspace/deemwar-one-os-wt-research  6b59e853 · 10212KB
   37s  ✓ ok    main                   …/ceo-workspace/deemwar-one-os             b479053b · 4213KB
   15m  ✓ ok    HEAD                   …/user/tmp                          689f4ec0 · 39KB
    2h  ✓ ok    feat/auto-demo-locale… …/deemwarworkspace/auto-demo               7d6497ba · 25176KB
    2h  ✓ ok    feat/mobile-voice-int… …/products/mobile                          45d3ce4a · 11313KB
    …

That's a real run on one of our machines — eighteen sessions across a dozen projects, most-recently-active first, each with its branch, age, transcript size, and a health flag. It scans ~120 project directories in about half a second, because almost all of that is stat calls; only the recently-active handful get opened.

Three flags, that's the whole interface:

  • default — sessions touched in the last 6 hours (the ones that matter right now),
  • --since 30 — tighten the window to the last 30 minutes,
  • --all — every session on the machine.

The health column is the point

AGO and SIZE are nice, but HEALTH is why we run it. It reads each session's tail and checks the one thing that silently bricks a Claude Code session: does the last assistant turn end in an orphaned thinking block? That's the thinking-block 400 wedge — an interrupted extended-thinking turn that gets re-sent and rejected on every continue. From inside the stuck session you can't tell what's wrong; from fleet-ps it's a red ⚠ wedged in a list, and the fix is one command away (claude-fleet rescue session fix …).

When you're babysitting one agent you'd notice it hang. When you've got a dozen, the wedged one just sits there quietly while you're looking at the other eleven. A fleet needs a fleet-level health check, and this is the cheapest one that exists: it's already in the file.

Why read the fleet, not just the session

This is the third tool we've made that starts by reading these transcripts, and the pattern is the point:

  • per session — what did this run do? (turns, tools, files)
  • per repo — which worktrees hold work a cleanup would delete?
  • per machine — what's running, and is any of it stuck? ← this one

Each is a short script over data that's already local and plaintext. There's no agent-fleet dashboard you need to install and feed; the observability is sitting in ~/.claude/projects/, waiting for someone to stat it. As fleets get bigger — and ours runs a lot of parallel agents in production — the whole-machine view is the one you end up keeping open.

The honest caveats

gitBranch is read from the start of the transcript, so a session that switched branches mid-run shows the branch it began on — accurate to the record, occasionally stale to reality. The slug is genuinely lossy (a dir named -a-b-c could be /a/b/c or /a-b/c), which is exactly why we read cwd from the file instead of decoding it. And it's a snapshot, not a daemon — it answers "right now," so you run it when you want to know, not on a timer. The schema is undocumented, so a future version can rename a field; the reader counts unknown shapes rather than crashing on them.

The real lesson

The recurring theme across all of this: your agents' state is local, plaintext, and queryable — you do not need a SaaS to see your own fleet. A directory of JSON-lines files, a few stat calls, and the head and tail of the active ones is enough to answer the questions that actually matter when you're running many agents at once. ps for your fleet was always there; you just had to read the folder.

We build and run autonomous agent fleets on Claude Code in production. If you're scaling past a handful of parallel agents, talk to us.


Get the tool → fleet-ps in agent-ops — open source (MIT), a single dependency-free script, part of our toolkit for running coding agents in production.

AI agentsobservabilityfleetops

Found this useful?