ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-05-04Intermediate

Automating Firebase Crashlytics Analysis with Antigravity

Use Antigravity to automate Firebase Crashlytics crash analysis. Covers BigQuery export setup, AI-powered root cause detection, and a GitHub Actions workflow for daily mobile crash reporting.

firebase11crashlytics5ios36android28mobile-devcrash-analysisgithub-actions9

Premium Article

Every Monday morning, the first tab I open is Crashlytics. And every Monday morning, the count is higher than it was on Friday.

If you're maintaining a mobile app solo, crash report triage is the kind of work that quietly piles up while you're focused on shipping features. Reading stack traces one by one, trying to reproduce, patching, repeat — it's draining in a way that's hard to explain until you've lived it.

I started using Antigravity to handle this differently. Instead of manually reading Crashlytics reports, I now pull the data via BigQuery, feed the structured crash context into Antigravity, and let it scan the codebase to pinpoint which method is responsible. This article walks through the exact workflow.

Setting Up Crashlytics BigQuery Export

Before anything else, enable the Crashlytics BigQuery export in your Firebase Console under Crashlytics → BigQuery export. Once enabled, crash events start flowing into a firebase_crashlytics dataset in BigQuery automatically.

Note: it takes 24–48 hours for the first data to appear after enabling. If you haven't set this up yet, do it now — it's a one-time configuration.

With the export running, here's a Python script to fetch your top 10 fatal crashes from the last 7 days:

# crash_fetcher.py
from google.cloud import bigquery
from datetime import datetime, timedelta
 
def get_top_crashes(project_id: str, app_id: str, days: int = 7) -> list[dict]:
    """
    Fetch the top 10 fatal crashes from Crashlytics via BigQuery.
 
    Prerequisites:
    - Firebase Crashlytics BigQuery export enabled
    - google-cloud-bigquery installed: pip install google-cloud-bigquery
    - GCP auth set up: gcloud auth application-default login
 
    Args:
        project_id: Your Firebase project ID (e.g., "my-app-12345")
        app_id: Your Firebase App ID from Project Settings > General
                Format: "1:123456789012:ios:abcdef1234567890"
        days: Number of days to look back (default 7)
    """
    client = bigquery.Client(project=project_id)
 
    # BigQuery table names replace ":" in app_id with "_"
    table_id = app_id.replace(":", "_")
    table = f"{project_id}.firebase_crashlytics.{table_id}"
 
    query = f"""
    SELECT
        issue_id,
        issue_title,
        issue_subtitle,
        COUNT(*) AS crash_count,
        ARRAY_AGG(
            STRUCT(
                application.display_version AS version,
                device.manufacturer AS manufacturer,
                device.model AS model,
                os.display_version AS os_version,
                ARRAY(
                    SELECT CONCAT(frame.file, ':', CAST(frame.line AS STRING))
                    FROM UNNEST(exceptions[OFFSET(0)].frames) AS frame
                    WHERE frame.file IS NOT NULL
                    LIMIT 10
                ) AS stack_frames
            )
            ORDER BY event_timestamp DESC
            LIMIT 3
        ) AS recent_examples
    FROM `{table}*`
    WHERE
        _TABLE_SUFFIX BETWEEN
            FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL {days} DAY))
            AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
        AND is_fatal = TRUE
    GROUP BY issue_id, issue_title, issue_subtitle
    ORDER BY crash_count DESC
    LIMIT 10
    """
 
    results = client.query(query).result()
    return [dict(row) for row in results]
 
 
if __name__ == "__main__":
    crashes = get_top_crashes(
        project_id="your-firebase-project-id",
        app_id="1:000000000000:ios:aaaaaaaaaaaaaaaa"
    )
    for crash in crashes:
        print(f"[{crash['crash_count']} crashes] {crash['issue_title']}")
    # Example output:
    # [247 crashes] Fatal Exception: NSInvalidArgumentException
    # [89 crashes] Fatal Exception: EXC_BAD_ACCESS SIGSEGV

Formatting Crash Data for Antigravity

The key insight here is that dumping raw crash output into Antigravity doesn't work as well as you'd hope. What actually helps is giving Antigravity a structured prompt that includes crash frequency, device context, and specific questions to answer.

# crash_analyzer.py
from crash_fetcher import get_top_crashes
 
def format_crash_for_ai(crash: dict) -> str:
    """
    Format a Crashlytics crash report as a structured Antigravity prompt.
 
    Adding frequency counts, device info, and explicit questions
    significantly improves Antigravity's analysis accuracy.
    Tested with NSInvalidArgumentException, EXC_BAD_ACCESS, NSRangeException.
    """
    examples = crash.get("recent_examples", [])
 
    # Collect unique device/OS combinations
    devices = list({
        f"{ex['manufacturer']} {ex['model']} (OS {ex['os_version']})"
        for ex in examples
        if ex.get("manufacturer")
    })[:3]
 
    # Stack frames from the most recent crash
    stack_frames = []
    if examples and examples[0].get("stack_frames"):
        stack_frames = examples[0]["stack_frames"]
 
    prompt = f"""
## Crashlytics Crash Analysis Request
 
**Occurrences (last 7 days):** {crash['crash_count']}
**Error type:** {crash['issue_title']}
**Subtitle:** {crash.get('issue_subtitle', 'N/A')}
**Primary devices affected:** {', '.join(devices) if devices else 'Unknown'}
 
**Stack trace (most recent crash, top 10 frames):**
{chr(10).join(f'  {i+1}. {frame}' for i, frame in enumerate(stack_frames))}
 
**Please:**
1. Identify the most likely root cause based on the stack trace
2. Point to the specific file(s) and method(s) in the codebase to investigate
3. Suggest three fixes in priority order
4. Estimate the minimal reproduction steps
"""
    return prompt
 
 
if __name__ == "__main__":
    crashes = get_top_crashes("your-firebase-project-id", "1:000000000000:ios:aaaaaaaaaaaaaaaa")
 
    # Output top 3 crashes as Antigravity prompts
    for i, crash in enumerate(crashes[:3], 1):
        print(f"\n{'=' * 60}\n### Crash #{i}")
        print(format_crash_for_ai(crash))

Paste the output into Antigravity's chat, and it will scan your codebase for relevant files before generating the analysis.

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
Triage crashes by real user impact (deduplicated installation_uuid), not raw count
Ready-to-use code to format both iOS and Android stack traces before handing them to Antigravity
Measured crash-free-rate and turnaround gains, plus three pitfalls of AI crash analysis
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

Antigravity 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

Agents & Manager2026-05-18
Splitting Daily Crashlytics Triage Across Five Antigravity Sub-Agents
Running six indie iOS and Android apps, the morning Crashlytics triage was draining me. I split the workflow across five Antigravity sub-agents (Fetcher, Classifier, Repro, Patch, PR) and locked their input and output to JSON schemas. Two weeks of production data shows where the human review boundary belongs.
App Dev2026-07-03
Catching Download Size Regressions Before Submission Day — A Weekly Agent Gate for AAB/IPA Size Budgets
Mediation SDKs and bundled assets quietly inflate download size. A design for size ledgers, budget gates, and agent-driven delta attribution using bundletool and App Thinning reports.
App Dev2026-06-22
Porting a Wallpaper Viewer's Slideshow and Page-Scrubber from iOS to Android — Where the Two-Way Sync with SnapHelper Tripped Me Up
A hands-on record of porting a full-screen wallpaper viewer's slideshow and bottom scrubber from iOS to Android. How I pinned down the current page with RecyclerView and SnapHelper, synced it two-way with the scrubber, and resolved the conflict between auto-advance and user input with a small state machine — in working Kotlin.
📚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 →