# Crash Triage

> Firebase Crashlytics의 상위 크래시를 조사해 스택과 코드를 대조하고 근본 원인까지 짚은 뒤, 각 건이 이미 등록된 이슈인지·새로 올릴 후보인지·건드릴 게 없는지 판정해 표로 보고하는 스킬. 다음 표현이 나오면 반드시 이 스킬을 사용한다: "크래시 확인해줘", "크래시 분석해줘", "Crashlytics 봐줘", "상위 이슈 분석", "요즘 무슨 크래시 나?", "크래시 리포트 정리해줘", "어떤 예외가 제일 많아?", "이 크래시 원인 찾아줘", "크래시 트리아지". 조사와 판정까지만 하고 **이슈 생성과 코드 수정은 하지 않는다** — 사용자가 고른 건을 이슈 스킬로 넘기고, 수정은 구현 스킬이 맡는다. Crashlytics MCP 도구가 붙어 있어야 동작한다.

- Skill: `gagip/crash-triage` (Agent Skill)
- Install (CLI): `npx skillmds@latest add gagip/crash-triage`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gagip/crash-triage/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: gagip (https://skillmd.com/u/gagip)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/gagip/crash-triage

---


# crash-triage

크래시 목록을 훑는 것과 원인을 아는 것은 다르다. 이 스킬은 **집계 → 개별 이벤트 → 코드 대조**를
끝까지 밀어 "무엇이 몇 건"이 아니라 "왜 터지고 누가 고쳐야 하나"로 답을 만든다.

**핵심 원칙**:

- **건수 순위는 심각도 순위가 아니다.** 1위가 사용자에게 무해한 잡음일 수 있고, 3건짜리가 강제
  종료일 수 있다. 순위와 별개로 치명도·현재성을 따로 판정한다.
- **이미 등록된 이슈를 다시 올리지 않는다.** 조사 결과의 절반은 "이건 이미 알고 있다"로 끝나는
  게 정상이다. 중복 확인을 건너뛰면 저장소에 쓰레기 이슈가 쌓인다.
- **원인은 코드를 읽고 말한다.** 스택만 보고 추정한 원인은 리포트에 쓰지 않는다. 읽지 못했으면
  "미확인"이라고 적는다.

---

## 실행 절차

### 1. 앱 ID를 찾는다

Crashlytics 도구는 전부 `appId`를 인자로 요구한다. 서버 설정에 프로젝트가 박혀 있지 않으므로
매 호출에 넘겨야 한다. 프로젝트 안에서 찾는다:

```bash
find . -name 'google-services.json' -o -name 'GoogleService-Info.plist' | grep -v node_modules
```

- Android: `google-services.json` → `client[].client_info.mobilesdk_app_id`
- iOS: `GoogleService-Info.plist` → `GOOGLE_APP_ID`

찾지 못하면 추측하지 말고 사용자에게 묻는다. 여러 앱이 나오면 어느 것을 볼지 확인하거나, 사용자가
범위를 안 정했으면 전부 순회하고 결과를 앱별로 나눠 보고한다.

### 2. 상위 이슈를 집계한다

`crashlytics_get_report`를 `report: "topIssues"`로 부른다. 기본은 최근 30일·상위 5건이고,
사용자가 기간이나 건수를 말했으면 그쪽을 쓴다. 조회 가능 범위는 최근 90일이며, 그보다 앞선
구간을 넣으면 거부된다.

집계만으로 판단이 안 서면 `topVersions`(특정 버전에 몰렸나) · `topAndroidDevices` ·
`topOperatingSystems`(기종·OS 편중인가)로 보조한다. 편중이 뚜렷하면 그 자체가 원인 단서다.

### 3. 이슈마다 실제 이벤트를 본다

`crashlytics_list_events`에 `filter.issueId`를 걸어 표본을 가져온다. 집계에는 없고 여기에만
있는 것들이 원인 규명의 핵심이다:

- **스택 트레이스** — 어디서 터졌나
- **커스텀 키** — 화면·로그인 상태·앱 버전 등 앱이 심어 둔 맥락
- **breadcrumb 로그** — 터지기 직전 무슨 일이 있었나(네트워크 응답 등)
- **기기·OS·앱 버전·이벤트 시각**

같은 이슈라도 표본을 2건 이상 보면 **무엇이 매번 달라지는지**가 드러난다. 이번 사례에서는
오류 메시지 안의 식별자 숫자가 매번 달라지는 것이 "요청마다 새로 발급되는 자원"이라는 결정적
단서였다. 하나만 보면 그 변주를 못 본다.

### 4. 코드와 대조한다

스택을 위에서 훑어 **우리 코드의 첫 프레임**을 찾고 그 파일을 실제로 읽는다. 거기서 호출 사슬을
거꾸로 따라가 "왜 그 상태가 됐나"까지 세운다. 그 지점의 주석이 지금도 사실인지 함께 본다 —
크래시는 주석에 적힌 가정이 틀렸다는 증거인 경우가 많다.

프레임이 외부 라이브러리뿐이면 우리가 못 고치는 결함일 수 있다. 그때는 라이브러리 버전을 확인해
알려진 수정본이 있는지, 우리 호출 방식이 그 결함을 유발하는지로 갈라 적는다. 난독화된 이름
(`a.b.c` 꼴)이 섞여 있으면 난독화 설정이 원인 후보다.

### 5. 이미 등록된 이슈인지 확인한다

오류 문구·기능 경로·API명·라이브러리명으로 **검색어를 여러 개** 만들어 전체 상태를 훑는다:

```bash
gh issue list --repo <owner/name> --search "<검색어>" --state all --limit 5 \
  --json number,state,title -q '.[] | "\(.number) \(.state) \(.title)"'
```

검색어 하나로 안 잡혔다고 "없다"고 단정하지 않는다. 존재는 한 번만 걸려도 성립하지만 부재는
여러 각도로 훑어야 성립한다. 관련 있어 보이는 이슈가 나오면 본문을 읽어 **같은 원인인지** 확인한다
— 증상이 비슷해도 뿌리가 다르면 별개 이슈다. 반대로 증상이 달라도 뿌리가 같으면 묶는다.

### 6. 판정해서 표로 낸다

건마다 아래 셋 중 하나로 판정한다:

- **기존 이슈** — 같은 원인의 이슈가 이미 열려 있다. 번호를 적는다.
- **신규 후보** — 등록된 게 없고 우리가 고칠 수 있다.
- **보류** — 외부 라이브러리 결함이거나, 재현 조건을 모르거나, 고칠 가치가 낮다. 이유를 적는다.

출력 형식:

```
## 크래시 조사 — <기간> · <앱 구분>

### 1위 — <제목> (<치명도>, <건수>건 / <영향 사용자>명)

**원인**: <코드를 읽고 세운 사슬. 파일:줄 인용>
**판정**: 신규 후보 / 기존 이슈 #N / 보류(<이유>)
**근거**: <스택·커스텀 키·breadcrumb 중 결정적이었던 것>

(건마다 반복)

### 정리

| 순위 | 무엇 | 치명도 | 판정 | 다음 할 일 |
|---|---|---|---|---|
```

마지막에 신규 후보를 이슈로 올릴지 사용자에게 묻는다. **직접 만들지 않는다** — 원인 판정이
틀렸을 때 잘못된 이슈가 저장소에 남는 비용이 크고, 무엇을 올릴지는 팀 사정을 아는 사람이 정한다.

---

## 행동 원칙

- **화면 이름은 예외가 기록된 시점의 것이다.** 지연 발화하는 오류(타이머·재시도·비동기 뒷정리)는
  요청이 시작된 화면이 아니라 몇 초 뒤 사용자가 머문 화면으로 찍힌다. 화면 이름을 재현 경로로
  단정하기 전에 이벤트 시각과 코드 흐름으로 교차 확인한다.
- **한 프로젝트의 여러 앱이 한 곳으로 섞여 들어올 수 있다.** 폰과 워치·태블릿이 패키지를 공유하면
  크래시가 같은 앱 ID로 올라온다. 스택의 패키지·Activity 이름으로 갈라 보고하지 않으면 엉뚱한
  코드베이스를 뒤지게 된다.
- **"처음 본 버전"과 "마지막에 본 버전"만 보고 옛날 버그로 넘기지 않는다.** 버전 표기는 배포
  주기에 따라 굳어 있을 수 있다. 현재성은 **이벤트 시각**으로 판단한다.
- **앱을 죽이지 않는 예외를 자동으로 낮게 보지 않는다.** 대부분의 크래시 리포터는 세션당 업로드
  건수에 상한이 있어, 잡음이 상한을 먹으면 같은 세션의 진짜 예외가 통째로 올라오지 않는다.
  가시성을 막는 잡음은 그 자체로 고칠 값어치가 있다.
- **읽기 전용이다.** 이 스킬이 쓰는 도구에는 이슈 상태 변경이나 노트 작성이 없다. 대시보드에서
  무언가를 바꿔야 하면 사용자에게 알리고 콘솔에서 하게 한다.
- 도구가 붙어 있지 않으면(조회가 전부 실패하면) 원인을 추측해 보고서를 만들지 말고, 플러그인이
  이 프로젝트에 설치돼 있는지 확인하라고 알린 뒤 멈춘다.

