[BUG] Claude Code 2.0.28 Freezes on Large API Responses - V8 JSON Parser + GC Thrashing (macOS)
Claude Code 2.0.28 Freezes on Large API Responses - V8 JSON Parser + GC Thrashing (macOS)
Summary
Claude Code node process freezes with 100% CPU when parsing large API responses (45k+ tokens), requiring force-kill. Root cause: V8 JSON parser performance collapse combined with garbage collection thrashing.
Environment
- OS: macOS 26.0.1 (25A362)
- Node: /opt/homebrew/Cellar/node/24.10.0/bin/node
- Claude Code: 2.0.28
- Platform: darwin (ARM64)
- Memory: Physical footprint 1.2GB (stable, but under GC pressure)
- Uptime at freeze: Immediate (froze during response parsing)
Bug Description
When Claude Code receives a large API response (~45k tokens displayed in status line), the process enters a death spiral:
- V8 JSON parser begins parsing deeply nested response objects
- Parser allocates large string buffers for UTF-8 decoding
- Triggers frequent garbage collection cycles
- GC cannot keep up, enters thrashing mode (constant
munmap/mmapsyscalls) - Process stuck at 100% CPU, completely unresponsive
- Only recovery:
kill -9 <PID>
Key symptom: Status line shows 45328 tokens - the response that triggered the freeze.
Root Cause Analysis
CPU Sample Evidence (5 seconds, 4060 total samples)
Call Stack Pattern:
uv_run → uv__io_poll → uv__stream_io →
node::fs::ReadFileUtf8 (reading API response from file/stream) →
Builtin_JsonParse →
JsonParser::ParseJsonObject (4060 samples) →
JsonParser::ParseJsonArray (nested arrays) →
JsonParser::ScanJsonString (114 samples - string scanning) →
JsonParser::DecodeString (string decoding/allocation)
Hot Functions:
- JsonParser::ParseJsonObject - 4060 samples (100% of time)
- Parsing deeply nested object hierarchies
- 207+ recursive calls into nested objects
- String Operations - 277 samples
ReadFileUtf8→String::NewFromUtf8(reading large response)- UTF-8 decoder:
Utf8Decoder::Decode<unsigned short>(126 samples) - Memory allocation for decoded strings
- Garbage Collection - 11 samples (but constant)
Heap::CollectGarbage→Scavenger::CollectGarbage__munmap/__mmapsyscalls (10 samples)DeleteMemoryChunk→VirtualMemory::Free- Pattern: Allocate → GC → Free → Repeat
Performance Anti-Pattern Detected:
- Large JSON response → massive string allocations
- Allocations trigger GC mid-parse
- GC frees memory but parser immediately allocates more
- Infinite loop: parse → allocate → GC → free → parse → ...
Memory Behavior
Physical footprint: 1.2GB (stable during freeze)
Peak: 1.6GB earlier in session
GC Activity:
- NewLargeObjectSpace allocations
- Frequent page munmap/mmap
- Memory churn without net growth (GC keeping up, but barely)
Conclusion: Not a memory leak - it's a performance collapse where JSON parser + GC are stuck in a thrashing loop.
Steps to Reproduce
Note: Reproduction is probabilistic - depends on API response size.
- Start Claude Code 2.0.28 in any project directory
- Ask a complex question that generates a large response (40k+ tokens)
- Observe status line showing high token count (45k+)
- Process freezes during response parsing
- Check Activity Monitor: node process at 100% CPU
sample <PID> 5will showJsonParser::ParseJsonObjectdominating samples
Trigger characteristics:
- Large, deeply nested JSON responses
- Typically occurs with detailed code analysis or long responses
- Token count visible in status line: 45k+ tokens
Expected Behavior
- JSON parser should handle large responses efficiently
- Or: stream processing to avoid loading entire response into memory
- Or: timeout/error after reasonable parse time
- Process should remain interruptible (Ctrl+C)
Actual Behavior
- Parser enters infinite loop with GC thrashing
- 100% CPU usage indefinitely
- Process unresponsive to Ctrl+C
- Only recovery:
kill -9 <PID> - All session context lost
Impact
- Severity: Critical - makes Claude Code unusable when triggered
- Frequency: Occasional (depends on response size)
- Workaround: None - must force-kill and restart
- Data loss: Current session context lost
Technical Evidence
Process Information
PID: 4858
Process: node (Claude Code 2.0.28)
CPU: 104.8%
Memory: 1.2GB physical footprint (peak 1.6GB)
Parent: shell wrapper process (PID 4648)
Sample Output (Abbreviated)
Call graph:
4060 Thread_400443993: claude
4060 uv_run → uv__io_poll → uv__stream_io
4060 ReadFileUtf8 (reading large API response)
4060 Builtin_JsonParse
4060 JsonParser<unsigned short>::Parse
4060 JsonParser::ParseJsonObject ← BOTTLENECK
4052 Nested recursive parsing
207 ParseJsonObject (nested objects)
114 ScanJsonString (string scanning)
GC Samples:
11 Heap::CollectGarbage
10 ScavengerCollector::CollectGarbage
10 NewLargeObjectSpace::FreeDeadObjects
10 MemoryAllocator::DeleteMemoryChunk
10 VirtualMemory::Free
10 __munmap (syscall)
String Allocation Samples:
242 ReadFileUtf8 → String::NewFromUtf8
126 Utf8Decoder::Decode<unsigned short> (UTF-8 decoding)
104 Utf8DecoderBase constructor
12 Factory::NewRawStringWithMap → HeapAllocator::AllocateRaw
Full Sample Data
Available in gist: https://gist.github.com/kuzmeech/c40a31a8b76e55f072a0e16d93e47209
Files:
process_info.txt- Process details and environmentsample_5s.txt- Full 5-second CPU sample with call stacks
Why This Is NOT a Duplicate
Analysis conducted: Reviewed all similar V8/performance issues from past 4 months to verify uniqueness.
Issue #4580 - "Claude Code freezes with 100% CPU during multi-agent task JSON serialization"
- Version: v1.0.61 (July 28, 2025) - 3 months old
- Root cause: JSON serialization (opposite direction)
- Stack:
v8::internal::JsonStringifier::Serialize_(2000+ recursive calls) - Triggered by: Task tool trying to serialize sub-agent responses
- Problem: Converting objects → JSON string
- Our issue (v2.0.28): JSON parsing (opposite direction)
- Stack:
JsonParser::ParseJsonObject(4060 samples) - Triggered by: Reading large API response
- Problem: Converting JSON string → objects
- Conclusion: Different code paths, different V8 subsystems (serializer vs parser)
Issue #8968 - "exceptional memory usage since v2.0.8"
- Version: v2.0.8 (October 5, 2025) - 23 days old
- Root cause: General memory growth (2.5-8GB per session)
- Stack: Not provided (no CPU sampling in report)
- Symptom: Progressive memory growth during normal usage
- Trigger: Any usage, memory accumulates over time
- Our issue (v2.0.28): Specific parsing bottleneck with stable memory
- Stack:
JsonParser+ GC thrashing clearly identified - Symptom: Immediate freeze at 100% CPU, memory stable at 1.2GB
- Trigger: Single large API response (45k tokens)
- Conclusion: Different symptoms (growth vs freeze), different memory patterns
Issue #10349 - "100% CPU usage - V8 StringTable performance collapse"
- Version: v2.0.27 (October 26, 2025) - 2 days old, reported by same user (kuzmeech)
- Root cause: V8 string internalization system
- Stack:
Array.map→SetKeyedProperty→StringTable::LookupString→String::SlowEquals - Bottleneck: 2541 samples in string hash table lookups
- Trigger: Building objects with many unique dynamic property names
- Pattern: Progressive degradation over 22 minutes, memory growth 3.2GB → 5.0GB
- Our issue (v2.0.28): V8 JSON parser
- Stack:
ReadFileUtf8→JsonParse→JsonParser::ParseJsonObject - Bottleneck: 4060 samples in JSON object parsing + GC
- Trigger: Reading large API response from stream
- Pattern: Immediate freeze during parse, memory stable 1.2GB
- Conclusion: Different V8 subsystems (StringTable vs JsonParser), different entry points (timer callback vs stream I/O), different memory behavior (growth vs stable)
Issue #1554 - "Generic freeze" (June 3, 2025, v1.0.10)
- Version: v1.0.10 - ~5 months old
- Analysis: No root cause identified, no CPU samples provided
- Conclusion: Too old, too vague, no technical analysis
Summary Table
| Issue | Version | Age | Root Cause | V8 Subsystem | Entry Point | Memory Pattern | Status |
|-------|---------|-----|------------|--------------|-------------|----------------|--------|
| #1554 | v1.0.10 | 5mo | Unknown | Unknown | Unknown | Unknown | Not analyzed |
| #4580 | v1.0.61 | 3mo | JSON serialization | JsonStringifier | Task tool | Unknown | Different direction |
| #8968 | v2.0.8 | 23d | Memory growth | Unknown | General usage | 2.5-8GB growth | Different symptom |
| #10349 | v2.0.27 | 2d | StringTable lookups | StringTable | Array.map (timer) | 3.2→5.0GB growth | Different subsystem |
| This | v2.0.28 | New | JSON parsing + GC | JsonParser | Stream I/O | 1.2GB stable | Unique issue |
Common thread across all: V8 engine scalability limits in Claude Code's Node.js architecture.
Why this is unique:
- Latest version (v2.0.28) - most current regression
- Specific trigger: Large API responses (45k+ tokens)
- Clear technical evidence: 5-second CPU sample with 4060 samples in JsonParser
- Distinct code path: Stream I/O → JSON parsing (not seen in other issues)
- Different memory behavior: Stable memory with GC thrashing (not growth)
Suggested Fixes
- Immediate (band-aid):
- Add response size limit before parsing
- Stream processing for large responses
- Timeout for JSON parse operations
- Better error message when parse hangs
- Short-term:
- Use streaming JSON parser (e.g.,
@streamparser/json) - Chunk large responses before parsing
- Increase V8 heap size limits
- Add progress indicator for large response parsing
- Long-term:
- Consider alternative runtime (see #9604 Golang rewrite proposal)
- Redesign response handling to avoid large in-memory JSON
- Implement backpressure on API responses
Related Issues
- #10349 - 100% CPU, V8 StringTable performance collapse (v2.0.27, Oct 26 2025, 2 days old - different trigger)
- #4580 - JSON serialization freeze during Task tool (v1.0.61, Jul 28 2025, 3 months old - opposite direction)
- #8968 - Exceptional memory usage since v2.0.8 (v2.0.8, Oct 5 2025, 23 days old - general memory issue)
- #1554 - Generic freeze (v1.0.10, Jun 3 2025, 5 months old - no root cause identified)
- #9604 - Proposal: Golang rewrite to address memory issues (architectural solution)
Additional Notes
- This is a regression - previous versions handled responses better (or responses were smaller)
- Issue may correlate with Sonnet 4.5 generating longer responses
- Not specific to any particular query type - just response size
- Affects normal interactive usage, not just edge cases
---
Files available if needed:
- Full process sample output (5s, ~30K lines)
- Session JSONL showing token count before freeze
- Activity Monitor screenshot showing 100% CPU
- Additional 10s sample for deeper analysis
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗