Summary
Several maps and buffers grow without bound across the codebase, causing memory leaks under sustained use.
Details
1. IP rate limits map (server.ts:130-146)
ipRateLimits (Map<string, number[]>) is never pruned. Every unique IP adds an entry that persists forever. timestamps.shift() on each check is O(n) for that IP's array.
2. Per-session event buffers (events.ts:68-69, 109-118)
eventBuffers stores up to 50 events per session but entries are never removed when a session ends. Only bulk-cleared in destroy(). The emitEnded method deletes the emitter but not the buffer.
3. Per-session metrics (metrics.ts:72-73)
perSession map grows indefinitely. sessionCompleted and sessionFailed increment counters but never remove the session entry. statusChanges array inside each entry also grows without bound.
4. Full JSONL reparse on every read (session.ts:976, 1023)
readTranscript() and getSummary() always parse the entire JSONL file from offset 0. For long-running sessions with multi-MB transcripts, this is O(n) per API call.
5. save() on every API read (session.ts:901)
readMessages() persists the full session state to disk on every GET request. Under load, this creates excessive disk I/O.
6. Excessive fixed sleeps in tmux (tmux.ts:325, 416, 505)
Creating a session takes ~8+ seconds in fixed sleeps alone (2000ms + 500ms + 1000-2000ms + up to 4800ms in retry delays). Should use polling instead of fixed delays.
7. windowExists overhead (tmux.ts:482-486)
Every sendKeys calls windowExists → listWindows → 3 tmux CLI invocations. With 3 retry attempts, that's up to 27 CLI calls just for existence checks per message.
8. activeSubagents array (session.ts:471-485)
Uses Array.includes() (O(n)) for dedup instead of Set.has() (O(1)). Persisted to state.json, growing the file.
9. SSE token count not decremented on consume (auth.ts:207-227)
validateSSEToken deletes consumed tokens but doesn't decrement sseTokenCounts, leaving counts artificially high until next cleanup cycle.
Suggested Fix
- Add periodic cleanup for
ipRateLimits (prune IPs with all timestamps older than window)
- Delete
eventBuffers entry alongside emitters in emitEnded
- Add
cleanupSession(sessionId) to MetricsCollector, call on session destroy
- Cache parsed JSONL entries; only read delta from byteOffset
- Debounce save for offset-only changes
- Replace fixed sleeps with polling loops in tmux
- Cache window existence with short TTL
- Use
Set<string> for activeSubagents
- Decrement
sseTokenCounts in validateSSEToken
Summary
Several maps and buffers grow without bound across the codebase, causing memory leaks under sustained use.
Details
1. IP rate limits map (
server.ts:130-146)ipRateLimits(Map<string, number[]>) is never pruned. Every unique IP adds an entry that persists forever.timestamps.shift()on each check is O(n) for that IP's array.2. Per-session event buffers (
events.ts:68-69, 109-118)eventBuffersstores up to 50 events per session but entries are never removed when a session ends. Only bulk-cleared indestroy(). TheemitEndedmethod deletes the emitter but not the buffer.3. Per-session metrics (
metrics.ts:72-73)perSessionmap grows indefinitely.sessionCompletedandsessionFailedincrement counters but never remove the session entry.statusChangesarray inside each entry also grows without bound.4. Full JSONL reparse on every read (
session.ts:976, 1023)readTranscript()andgetSummary()always parse the entire JSONL file from offset 0. For long-running sessions with multi-MB transcripts, this is O(n) per API call.5. save() on every API read (
session.ts:901)readMessages()persists the full session state to disk on every GET request. Under load, this creates excessive disk I/O.6. Excessive fixed sleeps in tmux (
tmux.ts:325, 416, 505)Creating a session takes ~8+ seconds in fixed sleeps alone (2000ms + 500ms + 1000-2000ms + up to 4800ms in retry delays). Should use polling instead of fixed delays.
7. windowExists overhead (
tmux.ts:482-486)Every
sendKeyscallswindowExists→listWindows→ 3 tmux CLI invocations. With 3 retry attempts, that's up to 27 CLI calls just for existence checks per message.8. activeSubagents array (
session.ts:471-485)Uses
Array.includes()(O(n)) for dedup instead ofSet.has()(O(1)). Persisted to state.json, growing the file.9. SSE token count not decremented on consume (
auth.ts:207-227)validateSSETokendeletes consumed tokens but doesn't decrementsseTokenCounts, leaving counts artificially high until next cleanup cycle.Suggested Fix
ipRateLimits(prune IPs with all timestamps older than window)eventBuffersentry alongside emitters inemitEndedcleanupSession(sessionId)to MetricsCollector, call on session destroySet<string>for activeSubagentssseTokenCountsinvalidateSSEToken