Summary
listSessions() from @anthropic-ai/claude-agent-sdk consumes ~900MB of memory per call because it spawns a full Claude Code CLI child process via stdio pipes to enumerate session files. This makes it unsuitable for periodic polling (e.g., a sidecar health check every 30 seconds).
Environment
@anthropic-ai/claude-agent-sdk: latest (installed via npm)
- Node.js: v24.13.1
- macOS (Apple Silicon)
- ~50+ session JSONL files in
~/.claude/projects/
Reproduction
Minimal sidecar server that calls listSessions() on an HTTP endpoint:
import { listSessions } from '@anthropic-ai/claude-agent-sdk'
// On startup: RSS = 77 MB
// After 2 calls to listSessions(): RSS = 950 MB
const sessions = await listSessions()
Experiment: memory timeline
| Time |
Event |
RSS |
| 0s |
Sidecar starts (Hono HTTP server, no SDK calls) |
77 MB |
| 3s |
First GET /sessions → calls listSessions() |
growing... |
| 8s |
Second GET /sessions → calls listSessions() |
950 MB |
| 5min |
Idle, no further calls, 0 active sessions |
1,340 MB |
Memory breakdown (via Node.js inspector protocol)
RSS: 1,340 MB
├─ V8 Heap: 522 MB
├─ External: 341 MB
│ └─ ArrayBuffers: 337 MB ← stdio pipe buffers
└─ Node overhead: ~77 MB
The 337 MB of ArrayBuffers strongly suggests stdio pipe buffering from the spawned CLI process. V8 does not return these pages to the OS after GC, so RSS ratchets up permanently with each call.
Control experiment
With listSessions() removed (only registry.list() for in-memory active sessions), the sidecar stays at 77 MB indefinitely.
Expected behavior
listSessions() should be a lightweight metadata query — reading file stats and first/last lines of JSONL files. It should not need to spawn a full Claude Code CLI process with its entire runtime just to enumerate sessions.
Ideally:
- < 10 MB memory overhead per call
- No child process spawn — direct filesystem scan
- Safe for polling every 10-30 seconds
Impact
Any application that periodically calls listSessions() (e.g., a session picker, dashboard, health monitor) will see memory grow to 1+ GB within seconds, even with zero active SDK sessions. The memory is never returned to the OS.
Workaround
We're replacing listSessions() with a direct filesystem scan of ~/.claude/projects/ to read session metadata, avoiding the SDK call entirely.
Summary
listSessions()from@anthropic-ai/claude-agent-sdkconsumes ~900MB of memory per call because it spawns a full Claude Code CLI child process via stdio pipes to enumerate session files. This makes it unsuitable for periodic polling (e.g., a sidecar health check every 30 seconds).Environment
@anthropic-ai/claude-agent-sdk: latest (installed via npm)~/.claude/projects/Reproduction
Minimal sidecar server that calls
listSessions()on an HTTP endpoint:Experiment: memory timeline
GET /sessions→ callslistSessions()GET /sessions→ callslistSessions()Memory breakdown (via Node.js inspector protocol)
The 337 MB of ArrayBuffers strongly suggests stdio pipe buffering from the spawned CLI process. V8 does not return these pages to the OS after GC, so RSS ratchets up permanently with each call.
Control experiment
With
listSessions()removed (onlyregistry.list()for in-memory active sessions), the sidecar stays at 77 MB indefinitely.Expected behavior
listSessions()should be a lightweight metadata query — reading file stats and first/last lines of JSONL files. It should not need to spawn a full Claude Code CLI process with its entire runtime just to enumerate sessions.Ideally:
Impact
Any application that periodically calls
listSessions()(e.g., a session picker, dashboard, health monitor) will see memory grow to 1+ GB within seconds, even with zero active SDK sessions. The memory is never returned to the OS.Workaround
We're replacing
listSessions()with a direct filesystem scan of~/.claude/projects/to read session metadata, avoiding the SDK call entirely.