CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-06-01Advanced

Grouping Crashes by Root Cause: A Triage Design Built on the Claude API

Crashlytics 'Issues' often scatter the same root cause across separate entries. After years of running apps with 50M+ cumulative downloads, here is how I use the Claude API to regroup crashes by actual root cause and rank them, with working code and real numbers.

Claude API115Crashlytics8Crash AnalysisIndie Development8Python17App Development5Batch Processing2

Premium Article

The more apps I shipped, the more the Crashlytics dashboard turned from something I read into something I merely glanced at.

I have been building apps on my own since 2014, and the catalog now adds up to more than 50 million cumulative downloads. As the number of apps, OS versions, and devices grew, Crashlytics started listing Issues by the hundreds. The hard part was never the sheer count. It was that the same root cause kept scattering across separate Issues.

For example, one image-decoding bug was split into three Issues simply because the calling thread differed. Conversely, obfuscated traces whose symbols had not resolved sometimes packed two genuinely different causes into a single Issue. Because Crashlytics grouping leans heavily on the top frame of the stack trace, this over-splitting and under-splitting happen at the same time.

Standing in front of that list every morning, the time I spent deciding what to tackle first quietly grew longer than the development itself. So I built a system on the Claude API that regroups crashes by root cause and produces a priority order mechanically. This article shares the design decisions, the working code, and the numbers I measured in practice.

Why "just let the LLM classify everything" fails

My first attempt was the naive one: hand raw stack traces to Claude and ask it to "group these." That breaks down for two reasons.

The first is cost. At a scale where thousands of crash events arrive per day, sending every one through an LLM is not financially realistic. The second is stability. Even for identical traces, the grouping drifts day to day depending on input ordering or slight wording changes. Once clusters change shape every day, you lose the ability to ask "is the cause I fixed yesterday still near the top today?"

To make this usable in real development, you need a clear split: process deterministically whatever can be processed deterministically, and use the LLM only at the 'boundary' where even a human would hesitate. I came to think of this design the way my grandfather, a temple carpenter, switched tools: ink-line marking by jig, but the cutting by hand. Dimensions that must not drift get fixed by a jig; only the parts that call for judgment get the hand. Crash analysis is the same — fingerprints that must not drift are fixed deterministically, and only the judgment of meaning is delegated to Claude.

That led to the following three stages.

  1. Normalize and fingerprint (deterministic) — strip the variable parts of a trace and build a stable fingerprint
  2. Pre-cluster by fingerprint (deterministic) — mechanically group identical fingerprints
  3. Label and merge with Claude (LLM) — bundle entries that share a cause despite different fingerprints, then assign a root-cause name and priority

Normalizing a stack trace into a fingerprint

Before anything reaches the LLM, I normalize each trace so that "same cause produces same string." The goal is to remove variable elements — memory addresses, line numbers, user IDs — and keep only a stable sequence of frames.

import re
import hashlib
 
# Rules to erase variable elements
NOISE_PATTERNS = [
    (re.compile(r"0x[0-9a-fA-F]+"), "0xADDR"),      # memory addresses
    (re.compile(r":\d+\)"), ":N)"),                   # line numbers
    (re.compile(r"\b\d{4,}\b"), "NUM"),               # long numeric IDs
    (re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"), "UUID"),
]
 
# Keep only the app's own frames (drop OS/SDK noise)
APP_FRAME_HINT = re.compile(r"(MyWallpaperApp|com\.dolice\.)")
 
def normalize_frame(frame: str) -> str:
    for pattern, repl in NOISE_PATTERNS:
        frame = pattern.sub(repl, frame)
    return frame.strip()
 
def fingerprint(stacktrace: str, top_n: int = 5) -> str:
    """Build a fingerprint from normalized, app-originated top frames."""
    frames = [normalize_frame(f) for f in stacktrace.splitlines() if f.strip()]
    app_frames = [f for f in frames if APP_FRAME_HINT.search(f)]
    # Fall back to top frames if no app frame is found
    key_frames = (app_frames or frames)[:top_n]
    digest = hashlib.sha256("\n".join(key_frames).encode()).hexdigest()
    return digest[:16]

The trick that pays off here is "prefer app-originated frames for the fingerprint." If you pick UIKit or libsystem frames from the top, crashes with different causes collapse into the same fingerprint. Anchoring on your own app's frames, by contrast, lets things split cleanly along cause boundaries. The official docs do not mention this, but in my experience this single move noticeably changed pre-cluster accuracy.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A three-stage design that normalizes and fingerprints stack traces deterministically, confining LLM calls to the 'boundary' to balance cost and stability
A complete, runnable Python implementation that labels root causes and merges clusters using the Claude API's structured output (tool use)
A priority-scoring formula based on affected users and crash-free-rate regression, plus operational notes on Batch API and prompt caching to keep costs low
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API & SDK2026-05-12
I Ran 1,000 App Store Reviews Through Claude API — Here's What My Data Was Hiding
Lessons from 10+ years of indie app development and 50M+ downloads: how to use Claude API to batch-analyze App Store reviews, auto-generate improvement priorities, and fix the blind spots human reading creates.
API & SDK2026-06-25
Reach a Remote MCP Server in a Single API Request: Implementing the Messages API MCP Connector
How to call a remote MCP server's tools using only the Messages API's mcp_servers and mcp_toolset—no local MCP client. Covers allowlist/denylist design, response handling, and the pitfalls to avoid before unattended production use.
API & SDK2026-06-13
Claude API Python Advanced Cookbook: 20 Production Patterns You'll Actually Use
20 battle-tested Python patterns for the Claude API—retry logic, parallel processing, cost optimization, testing, and monitoring. Copy-paste ready code recipes.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →