Crash in metrics collection (MLD function) when processing PDF with many images - context remains in history causing persistent timeout

Resolved 💬 3 comments Opened Dec 18, 2025 by patronsaint11-source Closed Feb 14, 2026

Description

Claude Code crashes in the internal metrics collection code (XLD → MLD function) after processing a PDF file containing many images. The crash persists even when performing subsequent unrelated operations because the PDF image data remains in the conversation history.

Root Cause Analysis

The crash occurs due to a persistent context pollution issue:

  1. Initial trigger: User reads a 6MB PDF file with many images using the Read tool
  2. Context pollution: PDF images are converted to multimodal content and stored in conversation history
  3. Persistent impact: Every subsequent message triggers metrics collection that must process the entire conversation history
  4. Timeout cascade: Large image data causes token counting (Y7H, FOI functions) to exceed the 1000ms timeout
  5. Complete failure: Even simple operations (reading Python files) crash because metrics collection still processes the PDF data in history

Why it persists after the initial PDF read:

  • PDF content remains in conversation history/context
  • Each tool call triggers metrics collection via XLD → MLD
  • Metrics collection scans the entire conversation context
  • Large PDF image data slows down this process beyond timeout limits

Steps to Reproduce

  1. Use Read tool to open a PDF file containing many images (~6MB or more)
  2. PDF is successfully read and converted to multimodal content
  3. Send any subsequent message or use any tool
  4. Metrics collection code attempts to calculate token counts
  5. Crash occurs - even for simple operations like reading text files
  6. Crash persists for all future operations in the same conversation

Crash Information

Stack trace location (approximate line 2890-2895):

async function MLD(H,$){
    if(QPL())return;
    let[{tools:A},L,I,D]=await Promise.all([UI$(H),VU($),t4(),VQ()]),
    B=D.gitStatus?.length??0,
    f=I.claudeMd?.length??0,
    E=B+f,
    G=vB();
    setTimeout(()=>G.abort(),1000);  // ⚠️ 1000ms timeout is too short for large contexts

    let M=a$(),X=UMH($),J=VMH(X,M),W=await kSH(M,G.signal,J),K=0,F=0,V=0,Z=0,T=0,P=L.filter((z)=>!z.isMcp);
    K=A.length,Z=P.length;
    let w=new Set;
    for(let z of A){
        let O=z.name.split("__");
        if(O.length>=3&&O[1])w.add(O[1])
    }
    F=w.size;

    try{
        // ⚠️ Crashes here when context contains large PDF images
        if(A.length>0){
            let{mcpToolTokens:z}=await Y7H(A,async()=>$,null);
            V=z
        }
        if(P.length>0)T=await FOI(L,async()=>$,null)
    }catch{}

    t("tengu_context_size",{
        git_status_size:B,
        claude_md_size:f,
        total_context_size:E,
        project_file_count_rounded:W,
        mcp_tools_count:K,
        mcp_servers_count:F,
        mcp_tools_tokens:V,
        non_mcp_tools_count:Z,
        non_mcp_tools_tokens:T
    })
}

function XLD(H,$,A){
    switch(H.name){
        case f7H:{
            let L=v3(A);
            if(L)return{...$,plan:L};
            return $
        }
        case wB.name:{
            let L=wB.inputSchema.parse($),
            {command:I,timeout:D,description:B,run_in_background:f}=L,
            E=I.replace(\`cd \${a$()} && \`,"")
            // Crash propagates through this call chain
        }
    }
}

Actual error snippet from crash:

2890 | ${Object.entries($).map(([A,L])=>\`# ${A}
2891 | ${L}\`).join(\`
2892 | \`)
2893 |
2894 |       IMPORTANT: this context may or may not be relevant to your tasks.
2895 | \`,isMeta:!0}),...H]}async function MLD(H,$){if(QPL())return;let[{tools:A},L,I,D]=await Promise.all([UI$(H),VU($),t4(),VQ()]),B=D.gitStatus?.length??0,f=I.claudeMd?.length??0,E=B+f,G=vB();setTimeout(()=>G.abort(),1000);let M=a$(),X=UMH($),J=VMH(X,M),W=await kSH(M,G.signal,J),K=0,F=0,V=0,Z=0,T=0,P=L.filter((z)=>!z.isMcp);K=A.length,Z=P.length;let w=new Set;for(let z of A){let O=z.name.split("__");if(O.length>=3&&O[1])w.add(O[1])}F=w.size;try{if(A.length>0){let{mcpToolTokens:z}=await Y7H(A,async()=>$,null);V=z}if(P.length>0)T=await FOI(L,async()=>$,null)}catch{}t("tengu_context_size",{git_status_size:B,claude_md_size:f,total_context_size:E,project_file_count_rounded:W,mcp_tools_count:K,mcp_servers_count:F,mcp_tools_tokens:V,non_mcp_tools_count:Z,non_mcp_tools_tokens:T})}function XLD(H,$,A){switch(H.name){case f7H:{let L=v3(A);if(L)return{...$,plan:L};return $}case wB.name:{let L=wB.inputSchema.parse($),{command:I,timeout:D,description:B,run_in_background:f}=L,E=I.replace(\`cd \${a$()} && \`,

Expected Behavior

Metrics collection should:

  1. Gracefully handle large contexts without crashing
  2. Skip metrics on timeout rather than failing the entire operation
  3. Not pollute conversation state - large content should be cleared from history
  4. Provide configuration to disable metrics collection if needed
  5. Use sampling instead of processing entire conversation history

Environment

  • OS: WSL2 (Linux 6.6.87.2-microsoft-standard-WSL2)
  • Node Version: v24.11.1
  • npm Version: 11.6.2
  • Claude Code Version: Latest (installed via npm)
  • Trigger: Reading PDF with many images (~6MB)
  • Persistence: Crash continues for all subsequent operations

Suggested Solutions

1. Add Comprehensive Error Handling (Critical)

Wrap metrics collection in try-catch to prevent crashes:

\\\javascript
async function MLD(H,$){
try {
// Existing metrics collection logic
if(QPL())return;
// ... rest of implementation
} catch (error) {
console.warn('Metrics collection failed, skipping:', error.message);
// Fail silently - metrics are non-critical
return;
}
}
\
\\

2. Increase or Remove Timeout (High Priority)

Current 1000ms timeout is insufficient for large contexts:

\\\`javascript
// Option A: Increase timeout based on context size
let timeout = Math.max(5000, Math.min(30000, E * 100));
setTimeout(()=>G.abort(), timeout);

// Option B: Make timeout configurable
let timeout = config.metricsTimeout || 10000;

// Option C: Remove timeout entirely for metrics
\\\`

3. Clear Large Content from History (Critical Fix)

Prevent context pollution by clearing large multimodal content:

\\\`javascript
// After processing large content
function shouldClearFromHistory(content) {
const hasLargeImages = / detect multiple images /;
const estimatedTokens = / rough estimate /;
return hasLargeImages || estimatedTokens > 50000;
}

if (shouldClearFromHistory(result)) {
clearFromHistory(messageId);
}
\\\`

4. Skip Token Counting for Large Contexts

\\\`javascript
let contextSize = estimateContextSize($);
let hasLargeContent = contextSize > 100000;

if(A.length>0 && !hasLargeContent){
let{mcpToolTokens:z}=await Y7H(A,async()=>$,null);
V=z;
} else {
V = -1; // Mark as skipped
}
\\\`

5. Implement Sampling

\\\javascript
if (Math.random() < 0.1 || shouldForceSample()) {
await MLD(H,$);
}
\
\\

6. Add Configuration Option

\\\json
{
"telemetry": {
"enabled": false,
"collectContextMetrics": false,
"metricsTimeout": 5000
}
}
\
\\

Impact

  • Severity: Critical
  • User Impact: Complete application crash, unable to continue work
  • Affected Operations: All operations after reading large PDFs
  • Workaround: Restart conversation (loses all context)
  • Frequency: 100% reproducible

Related Components

  • Crash location: XLD → MLD function chain
  • Related tool: Read tool (PDF processing)
  • Token counting: Y7H, FOI functions
  • Telemetry: t("tengu_context_size", ...)

Additional Context

Non-critical telemetry code should never crash the application. Recommendations:

  1. All telemetry must have comprehensive error handling
  2. Fail silently and log errors for debugging
  3. Consider async/background collection
  4. Implement context management to prevent history pollution
  5. Add user controls for telemetry

---

Reported by: patronsaint11@gmail.com
Date: 2025-12-18

View original on GitHub ↗

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