Skip to content

Commit da47a54

Browse files
authored
Merge branch 'code/cash' into dependabot/gradle/code/cash/org.jsoup-jsoup-1.22.2
2 parents bfe9bf2 + 6061847 commit da47a54

1,045 files changed

Lines changed: 2409 additions & 119363 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/triage/SKILL.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
name: triage
3+
description: >
4+
Triage a Bugsnag production issue — either the top open issue or a specific
5+
issue by URL/ID. Investigate with evidence from stack traces / logs /
6+
breadcrumbs, propose a fix direction, route through domain experts, and write
7+
a lean review brief.
8+
allowed-tools:
9+
- Bash
10+
- Read
11+
- Glob
12+
- Grep
13+
- Write
14+
- Edit
15+
- Agent
16+
- WebFetch
17+
- Skill
18+
---
19+
20+
# Triage: daily Bugsnag issue investigation
21+
22+
You are a senior Android engineer performing daily Bugsnag triage for the
23+
Flipcash Android app. Follow the steps below exactly.
24+
25+
## Step 1 — Fetch the issue
26+
27+
If the user provided a Bugsnag URL or error ID, pass it to the helper script:
28+
29+
```bash
30+
# Specific issue by URL
31+
bash .claude/skills/triage/scripts/bugsnag-top.sh --url "https://app.bugsnag.com/org/project/errors/abc123"
32+
33+
# Specific issue by error ID
34+
bash .claude/skills/triage/scripts/bugsnag-top.sh --error-id abc123
35+
```
36+
37+
Otherwise, fetch the top open issue automatically:
38+
39+
```bash
40+
bash .claude/skills/triage/scripts/bugsnag-top.sh
41+
```
42+
43+
The script emits a single JSON object with:
44+
45+
| Field | Description |
46+
|-------|-------------|
47+
| `error_id` | Bugsnag error group ID |
48+
| `error_url` | Deep link into the Bugsnag dashboard |
49+
| `event_url` | REST API URL for the latest event |
50+
| `title` | Error class + message |
51+
| `severity` | `error` / `warning` / `info` |
52+
| `events` | Total occurrences |
53+
| `users` | Unique affected users |
54+
| `first_seen` | ISO-8601 timestamp |
55+
| `release` | `app_version` from the latest event |
56+
57+
If the script exits non-zero, stop and report the error to the user.
58+
59+
## Step 2 — Pull event detail
60+
61+
Fetch `event_url` (include header `Authorization: token $BUGSNAG_TOKEN`).
62+
Source `.env` from the repo root if the variable is not already set.
63+
64+
Parse the response using the event shape documented in
65+
`.claude/skills/triage/references/event-shape.md`.
66+
67+
Extract four evidence sources:
68+
69+
1. **Stack trace**`exceptions[0].stacktrace` (frames with `file`, `method`,
70+
`lineNumber`, `inProject`)
71+
2. **Exception info**`exceptions[0].errorClass` + `exceptions[0].message`
72+
3. **App logs**`metaData["App Logs"]["app_log"]` (single string, last ~64 KB
73+
of log output)
74+
4. **Breadcrumbs**`breadcrumbs[]` (timestamped UI / state / network events)
75+
76+
## Step 3 — Map stack frames to source
77+
78+
Android stack frames use Java/Kotlin package-qualified class names (e.g.
79+
`com.flipcash.features.cash.CashViewModel`). To locate the source file:
80+
81+
1. Convert the class name to a path fragment: replace `.` with `/` and append
82+
`.kt` (try `.java` as a fallback).
83+
2. Use `Glob` to find the file in the repo (e.g. `**/**/CashViewModel.kt`).
84+
3. Read the relevant lines (`lineNumber` from the frame +/- 30 lines of
85+
context).
86+
87+
Only map frames where `inProject` is `true`.
88+
89+
## Step 4 — Build the evidence timeline
90+
91+
### 4a. Version check
92+
93+
Read `.well-known/release-manifest.json` to get the current production and
94+
internal release versions:
95+
96+
```json
97+
{
98+
"tracks": {
99+
"production": { "versionCode": 3508, "versionName": "2026.5.2" },
100+
"internal": { "versionCode": 3508, "versionName": "2026.5.2" }
101+
}
102+
}
103+
```
104+
105+
Compare the event's `app.version` against `tracks.production.versionName` to
106+
determine if the crash still affects the latest release.
107+
108+
### 4b. Assemble evidence
109+
110+
Collect:
111+
112+
- The in-project stack frames mapped to source (file:line + code snippet)
113+
- The exception class and message
114+
- Relevant log lines (grep the `app_log` string for keywords from the exception)
115+
- The last 10-20 breadcrumbs before the crash
116+
- `app.version`, `device.manufacturer`, `device.model`, `os.version`
117+
118+
## Step 5 — Investigate root cause
119+
120+
Using the evidence from Step 4:
121+
122+
1. Read the source files identified in the stack trace.
123+
2. Follow the call chain — read callers and callees within 2 hops.
124+
3. Check for known patterns: null-safety violations, lifecycle issues,
125+
threading bugs, uncaught coroutine exceptions, missing error handling.
126+
4. Form a hypothesis and verify it against the logs and breadcrumbs.
127+
128+
## Step 6 — Propose a fix direction
129+
130+
Write a concrete fix direction (NOT a full implementation):
131+
132+
- Which file(s) to change and roughly where (`.kt:NN`)
133+
- What the fix involves (e.g. "add null check before accessing X",
134+
"move coroutine launch to lifecycleScope", "catch Y in Z")
135+
- Why this addresses the root cause
136+
- Any risks or side effects
137+
138+
## Step 7 — Route through domain experts
139+
140+
Based on the evidence, tag relevant experts by adding their labels to the brief.
141+
An issue may match multiple experts.
142+
143+
| Expert | Trigger |
144+
|--------|---------|
145+
| `compose` | Touches files with `@Composable`, `Modifier`, `remember`, `LaunchedEffect`, or under `ui/`, `features/*/ui/` |
146+
| `kotlin-coroutines` | Stack contains `CoroutineScope`, `Dispatchers`, `suspend`, `launch`, `async`, `withContext`, `Job`, `SupervisorJob` |
147+
| `android-tdd` | Proposed fix direction adds or modifies test files |
148+
| `kotlin-flows` | Stack or fix involves `Flow`, `StateFlow`, `SharedFlow`, `collect`, `stateIn` |
149+
150+
## Step 8 — Write the review brief
151+
152+
Use the template in `.claude/skills/triage/references/brief-template.md`.
153+
154+
Save the brief to `.claude/plans/triage-<error_id>.md`.
155+
156+
Keep the brief under 300 words (excluding code snippets and the evidence
157+
appendix).
158+
159+
## Step 9 — Next steps
160+
161+
If the fix direction is clear and well-scoped, offer to draft an implementation
162+
plan using `superpowers:writing-plans`.
163+
164+
If the root cause is ambiguous, suggest specific debugging steps (add logging,
165+
reproduce locally, check related Bugsnag issues).
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Triage Brief: {{title}}
2+
3+
| Field | Value |
4+
|-------|-------|
5+
| **Bugsnag** | [{{error_id}}]({{error_url}}) |
6+
| **Severity** | {{severity}} |
7+
| **Events / Users** | {{events}} / {{users}} |
8+
| **First seen** | {{first_seen}} |
9+
| **Release** | {{release}} |
10+
| **Production versionName** | {{production_version}} |
11+
| **Experts** | {{experts}} |
12+
13+
## Root Cause
14+
15+
{{1-3 sentences explaining the root cause, referencing specific `.kt:NN` locations}}
16+
17+
## Evidence
18+
19+
- **Exception**: `{{errorClass}}`: {{message}}
20+
- **Key stack frame**: `{{file.kt:NN}}``{{method}}`
21+
- **Log excerpt**: `{{relevant log line(s)}}`
22+
- **Breadcrumb trail**: {{last N breadcrumbs summarized}}
23+
- **Device**: {{manufacturer}} {{model}}, Android {{os_version}}
24+
25+
## Fix Direction
26+
27+
{{Concrete description of what to change and where (`.kt:NN`), why it fixes the
28+
issue, and any risks. NOT a full implementation — just the direction.}}
29+
30+
## Appendix: Stack Trace (in-project frames)
31+
32+
```
33+
{{mapped in-project frames with file:line and method}}
34+
```
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Bugsnag Event Shape — Android
2+
3+
A single event fetched from `GET /events/{event_id}` (with project auth).
4+
5+
## Four Evidence Sources
6+
7+
### 1. App Logs — `metaData["App Logs"]["app_log"]`
8+
9+
A single string containing the last ~64 KB of log output captured at crash time.
10+
Attached by `FlipcashBugsnagErrorCallback` in
11+
`apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/debug/FlipcashBugsnagErrorCallback.kt`.
12+
13+
Unlike iOS's structured `metaData.app_logs.recent_logs` array, this is
14+
unstructured text. Grep it for keywords from the exception to find relevant
15+
context.
16+
17+
### 2. Stack Trace — `exceptions[0].stacktrace`
18+
19+
Array of frame objects:
20+
21+
```json
22+
{
23+
"file": "com/flipcash/features/cash/CashViewModel.kt",
24+
"method": "com.flipcash.features.cash.CashViewModel.loadBalance",
25+
"lineNumber": 42,
26+
"inProject": true,
27+
"columnNumber": null
28+
}
29+
```
30+
31+
- `file` — path-like representation of the Kotlin/Java source file using
32+
package-qualified slashes (e.g. `com/flipcash/features/cash/CashViewModel.kt`)
33+
- `method` — fully qualified method name with package
34+
- `lineNumber` — source line (may be approximate after R8/ProGuard)
35+
- `inProject``true` for app code, `false` for framework / library code
36+
37+
**Path mapping**: Android frames use Java package paths. To find the source
38+
file, either:
39+
- Convert to a glob: `**/CashViewModel.kt` and search the repo
40+
- Or convert dots to slashes and search: `com/flipcash/features/cash/CashViewModel.kt`
41+
42+
### 3. Breadcrumbs — `breadcrumbs[]`
43+
44+
Array of timestamped events (same structure as iOS):
45+
46+
```json
47+
{
48+
"timestamp": "2026-05-10T14:23:01.000Z",
49+
"name": "Navigate to CashScreen",
50+
"type": "navigation",
51+
"metaData": { "route": "/cash" }
52+
}
53+
```
54+
55+
Types correspond to `BreadcrumbType` values: `ERROR`, `LOG`, `NAVIGATION`,
56+
`REQUEST`, `PROCESS`, `STATE`, `USER` (see `BugsnagBreadcrumbSink`).
57+
58+
### 4. Exception Info — `exceptions[0]`
59+
60+
```json
61+
{
62+
"errorClass": "java.lang.NullPointerException",
63+
"message": "Attempt to invoke virtual method 'void ...' on a null object reference",
64+
"type": "android"
65+
}
66+
```
67+
68+
Replaces iOS's `nserror` concept. The `errorClass` is the Java/Kotlin exception
69+
class name; `message` is the detail string.
70+
71+
For Kotlin-specific exceptions, look for:
72+
- `kotlin.KotlinNullPointerException`
73+
- `kotlinx.coroutines.JobCancellationException`
74+
- `java.util.concurrent.CancellationException`
75+
- `IllegalStateException` (often lifecycle-related)
76+
77+
## Secondary Context
78+
79+
| Path | Notes |
80+
|------|-------|
81+
| `app.version` | versionName (e.g. `2026.5.3`) |
82+
| `app.versionCode` | Integer version code |
83+
| `app.releaseStage` | `production` / `development` |
84+
| `device.manufacturer` | e.g. `Samsung`, `Google` |
85+
| `device.model` | e.g. `Pixel 8`, `SM-S918B` |
86+
| `device.osVersion` | Android version string (e.g. `14`) |
87+
| `device.totalMemory` | Total RAM in bytes |
88+
| `device.freeMemory` | Free RAM at crash time |
89+
| `user.id` | Anonymized user identifier |
90+
| `session` | Session start, events handled/unhandled |
91+
| `featureFlags[]` | Active feature flags at crash time |
92+
93+
## Filtering Noise
94+
95+
The app's error callback (`FlipcashBugsnagErrorCallback`) already filters:
96+
- gRPC status codes in `ErrorUtils.ignoredGrpcStatusCodes` (transport, validation)
97+
- Handled gRPC `INTERNAL` errors
98+
99+
So events that reach Bugsnag are either unhandled crashes or explicitly notified
100+
errors that passed the filter.

0 commit comments

Comments
 (0)