AI autopilot for Firebase Crashlytics.
Reads crashes → fixes code → opens PRs. Automatically. Every day.
🔥 Firebase Crashlytics
│
│ BigQuery export
▼
📊 BigQuery Table
│
│ fetch-crashlytics.py (new · unresolved · regressed)
▼
📋 crash_issues.json
│
│ claude -p "..." → Read · Grep · Edit your source files
▼
🔧 Code changes + confidence rating
│
│ HIGH / MEDIUM → open PR
│ LOW / SKIP → discard
▼
📬 Pull Request (impact table · root cause · Firebase Console link)
Each daily run:
- 🔍 Fetches top crashes from BigQuery — new issues, high-impact unresolved, and regressions
- 🤖 Runs Claude Code per crash — reads your source files, traces the call path, attempts a fix
- 🎯 Rates confidence as
HIGH,MEDIUM,LOW, orSKIP— you set the bar - 📬 Opens a PR with impact stats, root cause analysis, and a direct Firebase Console link
| Platform | Stack Trace | |
|---|---|---|
| ✅ Supported | threads[] crashed thread frames |
|
| ✅ Supported | exceptions[].frames |
|
| ✅ Supported | Native frames + JS source files when readable (see details) | |
| Flutter | 🔜 Coming soon | — |
In the Firebase Console:
Crashlytics → BigQuery Export → Enable both toggles
- ✅ Export Crashlytics data to BigQuery
- ✅ Enable streaming export (for near real-time data, otherwise ~24h delay)
Firebase auto-creates the firebase_crashlytics dataset in your GCP project.
# Create the service account
gcloud iam service-accounts create crash-pilot \
--display-name "Crash Pilot"
# Grant read access to BigQuery
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:crash-pilot@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/bigquery.dataViewer"
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:crash-pilot@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/bigquery.jobUser"
# Download the key
gcloud iam service-accounts keys create key.json \
--iam-account=crash-pilot@YOUR_PROJECT_ID.iam.gserviceaccount.com💡 The
project_idis extracted automatically from the key — no extra variable needed.
Replace dots with underscores in your bundle / package ID:
| Platform | Bundle ID | BigQuery Table |
|---|---|---|
| iOS | com.mycompany.myapp |
com_mycompany_myapp |
| Android | com.mycompany.myapp |
com_mycompany_myapp |
Verify in BigQuery Studio → your project → firebase_crashlytics dataset.
claude auth login
cat ~/.claude/credentials.json | jq -r '.claudeAiOauth.accessToken'Requires a Claude Code subscription.
Go to Settings → Secrets and variables → Actions in your repo:
| Name | Type | Value |
|---|---|---|
GCP_SA_KEY |
🔒 Secret | Contents of key.json |
CLAUDE_CODE_OAUTH_TOKEN |
🔒 Secret | Claude Code OAuth token |
BIGQUERY_TABLE |
📦 Variable | e.g. com_mycompany_myapp |
BUNDLE_ID |
📦 Variable | (optional) e.g. com.mycompany.myapp |
Copy examples/workflow.yml to .github/workflows/crash-pilot.yml:
- uses: your-org/crash-pilot@v1
with:
bigquery_table: ${{ vars.BIGQUERY_TABLE }}
gcp_credentials_json: ${{ secrets.GCP_SA_KEY }}
claude_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
platform: ios # ios | android | react-native
⚠️ Required: checkout must usefetch-depth: 0- uses: actions/checkout@v4 with: fetch-depth: 0
| Input | Required | Default | Description |
|---|---|---|---|
bigquery_table |
✅ | — | BigQuery table name (e.g. com_myapp_ios) |
gcp_credentials_json |
✅ | — | Service account JSON — project_id extracted automatically |
claude_token |
✅ | — | Claude Code OAuth token |
github_token |
✅ | — | secrets.GITHUB_TOKEN |
bigquery_dataset |
firebase_crashlytics |
BigQuery dataset name | |
platform |
ios |
ios · android · react-native |
|
bundle_id |
"" |
App bundle ID — enables Firebase Console deep links in PRs | |
lookback_hours |
24 |
Hours to look back for new / regressed crashes | |
max_issues |
5 |
Max issues per run (keep low to control costs) | |
min_confidence |
MEDIUM |
Minimum confidence to open a PR: HIGH · MEDIUM · LOW |
|
default_branch |
main |
Base branch for fix branches and PRs | |
pr_label |
crash-pilot |
Label applied to generated PRs | |
claude_timeout |
300 |
Seconds before killing Claude per issue | |
dry_run |
false |
Analyse crashes but skip PR creation | |
allow_new_files |
false |
Allow Claude to create new source files |
Claude self-rates every fix before a PR is opened:
| Level | Meaning | Action |
|---|---|---|
🟢 HIGH |
Exact crash site found, fix is unambiguous | PR opened |
🟡 MEDIUM |
Likely cause understood, minor uncertainty | PR opened |
🟠 LOW |
Cannot confidently identify root cause | Skipped |
⛔ SKIP |
Third-party crash, runtime-only, or no source found | Skipped |
Set min_confidence: HIGH for a conservative setup. LOW is not recommended for production repos.
| Service | Cost per run |
|---|---|
| BigQuery | ~$0.01–$0.10 · 3 queries with partition pruning |
| Claude Code | ~$0.05–$0.50 per issue · 5k–50k tokens depending on codebase size |
| Total (5 issues/day) | ~$0.25–$2.60/day |
React Native stacks mix native (ObjC/Java/C++) and JavaScript frames. crash-pilot handles both layers:
| Crash Layer | Blame Frame Looks Like | Result |
|---|---|---|
| ✅ JS source | src/screens/Home.tsx:42 |
Claude reads the file and attempts a fix |
| ✅ Native module | MyModule.java:88, RCTCamera.m:34 |
Claude reads the native file and fixes it |
| ⛔ Compiled bundle | index.android.bundle:1:234567 |
SKIP — source maps not in repo |
| ⛔ Hermes bytecode | .hbc file or very large column offset |
SKIP — compiled binary |
💡 JS crashes are fixable when your source files are readable from the blame frame. This works for apps that don't minify in CI, monorepos where the RN source is in the same repo, and any setup where the
.tsx/.ts/.jspath in the stack trace matches a real file in the codebase.
- Runs entirely inside your GitHub Actions runner
- Claude only has
Read,Glob,Grep,Edit,Writeaccess — no shell execution - Fix branches are
crash-pilot/*— PRs require human review before merge - Service account credentials are never written to disk
BigQuery schema error — error_type / is_fatal not found
crash-pilot auto-detects which column your export uses (error_type vs is_fatal). If detection fails it falls back to error_type = 'FATAL'. Check the logs for Fatality SQL: to see which path was taken.
Claude didn't produce /tmp/fix_summary.md
Claude timed out or errored. The action emits a ::warning:: annotation with the last 20 lines of output. Increase claude_timeout or check if the codebase is too large to traverse within the limit.
PR step is skipped silently
Ensure fetch-depth: 0 is set in your checkout step. Without it git rev-list can't compare the fix branch with the remote default branch.
BigQuery permission denied
The service account needs both roles/bigquery.dataViewer and roles/bigquery.jobUser. dataViewer alone is not enough to run queries.
PR label creation fails
The action auto-creates the label. If it fails, the PR is still opened without a label. Verify your github_token has pull-requests: write.
To add a new platform:
- Add
scripts/prompts/<platform>.txt— must end with$BASE_TEMPLATE - Add a case in
scripts/process-crashes.shforPLATFORM_TEMPLATE - Add the stack trace SQL variant in
get_stack_trace_sql()inscripts/fetch-crashlytics.py - Update the
platforminput inaction.ymland this README
MIT