[BUG] Claude Code 2.0.28 Freezes on Large API Responses - V8 JSON Parser + GC Thrashing (macOS)

Resolved 💬 2 comments Opened Oct 28, 2025 by kuzmeech Closed Nov 25, 2025

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:

  1. V8 JSON parser begins parsing deeply nested response objects
  2. Parser allocates large string buffers for UTF-8 decoding
  3. Triggers frequent garbage collection cycles
  4. GC cannot keep up, enters thrashing mode (constant munmap/mmap syscalls)
  5. Process stuck at 100% CPU, completely unresponsive
  6. 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:

  1. JsonParser::ParseJsonObject - 4060 samples (100% of time)
  • Parsing deeply nested object hierarchies
  • 207+ recursive calls into nested objects
  1. String Operations - 277 samples
  • ReadFileUtf8String::NewFromUtf8 (reading large response)
  • UTF-8 decoder: Utf8Decoder::Decode<unsigned short> (126 samples)
  • Memory allocation for decoded strings
  1. Garbage Collection - 11 samples (but constant)
  • Heap::CollectGarbageScavenger::CollectGarbage
  • __munmap / __mmap syscalls (10 samples)
  • DeleteMemoryChunkVirtualMemory::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.

  1. Start Claude Code 2.0.28 in any project directory
  2. Ask a complex question that generates a large response (40k+ tokens)
  3. Observe status line showing high token count (45k+)
  4. Process freezes during response parsing
  5. Check Activity Monitor: node process at 100% CPU
  6. sample <PID> 5 will show JsonParser::ParseJsonObject dominating 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 environment
  • sample_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.mapSetKeyedPropertyStringTable::LookupStringString::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: ReadFileUtf8JsonParseJsonParser::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:

  1. Latest version (v2.0.28) - most current regression
  2. Specific trigger: Large API responses (45k+ tokens)
  3. Clear technical evidence: 5-second CPU sample with 4060 samples in JsonParser
  4. Distinct code path: Stream I/O → JSON parsing (not seen in other issues)
  5. Different memory behavior: Stable memory with GC thrashing (not growth)

Suggested Fixes

  1. 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
  1. 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
  1. 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

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗