Skip to content

Commit 48c24f8

Browse files
committed
chore(triage): add build-lookup and r8-mapping skills, wire into triage
Add two new Claude Code skills: - build-lookup: resolves a versionCode to its git commit and GitHub Actions run - r8-mapping: downloads the R8 mapping file from CI artifacts for a given build Update the triage skill with Step 3a to automatically deobfuscate R8-obfuscated stack traces when Bugsnag fails to apply the mapping. Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent f7e0ab6 commit 48c24f8

5 files changed

Lines changed: 398 additions & 0 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
name: build-lookup
3+
description: >
4+
Look up the git commit and GitHub Actions run for a Flipcash versionCode.
5+
Usage: /build-lookup <versionCode>
6+
user-invocable: true
7+
argument-hint: "<versionCode>"
8+
allowed-tools:
9+
- Bash
10+
---
11+
12+
# Build Lookup: versionCode to commit + CI run
13+
14+
Resolve a Flipcash versionCode to its git commit and the associated GitHub
15+
Actions build run.
16+
17+
## Background
18+
19+
The Flipcash versionCode is the output of `git rev-list --count HEAD` at build
20+
time (see `apps/flipcash/app/build.gradle.kts:20-25`). This means
21+
**versionCode N = the Nth commit from the root of the repository**.
22+
23+
When someone says "commit 3797" or "build 3797" they mean versionCode 3797.
24+
25+
## Usage
26+
27+
Parse `$ARGUMENTS` for a versionCode (integer). If not provided, ask the user.
28+
29+
Run the helper script:
30+
31+
```bash
32+
bash .claude/skills/build-lookup/scripts/build-lookup.sh <versionCode>
33+
```
34+
35+
The script emits JSON:
36+
37+
```json
38+
{
39+
"version_code": 3797,
40+
"commit": {
41+
"sha": "637722b9c...",
42+
"short": "637722b9c",
43+
"message": "the commit message",
44+
"date": "2026-06-03T..."
45+
},
46+
"total_commits": 3810,
47+
"github_actions": {
48+
"run_id": 26846060877,
49+
"url": "https://github.com/code-payments/code-android-app/actions/runs/26846060877",
50+
"status": "completed",
51+
"conclusion": "success"
52+
}
53+
}
54+
```
55+
56+
`github_actions` is `null` if no matching workflow run was found.
57+
58+
## Output
59+
60+
Present the results clearly:
61+
62+
- **versionCode**: N
63+
- **Commit**: `<short_sha>``<message>` (date)
64+
- **GitHub Actions**: [Run #ID](url) — status/conclusion
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env bash
2+
# build-lookup.sh — Resolve a versionCode to its git commit and GitHub Action run.
3+
#
4+
# The Flipcash versionCode is derived from `git rev-list --count HEAD` (see
5+
# apps/flipcash/app/build.gradle.kts:20-25). versionCode N = the Nth commit
6+
# from the root of the repo.
7+
#
8+
# Usage:
9+
# ./build-lookup.sh <versionCode>
10+
# ./build-lookup.sh 3797
11+
#
12+
# Output: JSON with commit_sha, commit_short, commit_message, and (if found)
13+
# the GitHub Actions run ID and URL for the Flipcash2 workflow.
14+
15+
set -euo pipefail
16+
17+
if [[ $# -lt 1 ]]; then
18+
echo "Usage: build-lookup.sh <versionCode>" >&2
19+
exit 1
20+
fi
21+
22+
VERSION_CODE="$1"
23+
WORKFLOW_ID="229420296" # "Flipcash2 Build and Deploy"
24+
25+
# ── Resolve versionCode to commit ───────────────────────────────────
26+
# versionCode = git rev-list --count HEAD at the time of build.
27+
# The Nth commit (1-indexed from root) is at position N in `git rev-list --reverse HEAD`.
28+
# Equivalently: git rev-list HEAD | sed -n '<offset>p' where offset = total - N + 1.
29+
30+
TOTAL=$(git rev-list --count HEAD)
31+
32+
if (( VERSION_CODE > TOTAL )); then
33+
echo "ERROR: versionCode $VERSION_CODE exceeds current commit count ($TOTAL)" >&2
34+
exit 1
35+
fi
36+
37+
if (( VERSION_CODE < 1 )); then
38+
echo "ERROR: versionCode must be >= 1" >&2
39+
exit 1
40+
fi
41+
42+
OFFSET=$(( TOTAL - VERSION_CODE + 1 ))
43+
COMMIT_SHA=$(git rev-list HEAD | sed -n "${OFFSET}p")
44+
COMMIT_SHORT="${COMMIT_SHA:0:9}"
45+
COMMIT_MESSAGE=$(git log -1 --format='%s' "$COMMIT_SHA")
46+
COMMIT_DATE=$(git log -1 --format='%aI' "$COMMIT_SHA")
47+
48+
# ── Find GitHub Actions run for this commit ─────────────────────────
49+
RUN_JSON=""
50+
RUN_ID=""
51+
RUN_URL=""
52+
RUN_STATUS=""
53+
RUN_CONCLUSION=""
54+
55+
if command -v gh &>/dev/null; then
56+
# gh run list --commit requires a full SHA match, so fetch recent runs and
57+
# filter by headSha prefix instead. We pull the last 100 runs to cover ~2
58+
# weeks of builds.
59+
ALL_RUNS=$(gh run list \
60+
--workflow="$WORKFLOW_ID" \
61+
--json databaseId,url,status,conclusion,headSha \
62+
--limit 100 2>/dev/null || true)
63+
64+
if [[ -n "$ALL_RUNS" ]]; then
65+
MATCH=$(echo "$ALL_RUNS" | jq --arg sha "$COMMIT_SHA" '[.[] | select(.headSha == $sha)] | .[0] // empty')
66+
if [[ -n "$MATCH" ]]; then
67+
RUN_ID=$(echo "$MATCH" | jq -r '.databaseId')
68+
RUN_URL=$(echo "$MATCH" | jq -r '.url')
69+
RUN_STATUS=$(echo "$MATCH" | jq -r '.status')
70+
RUN_CONCLUSION=$(echo "$MATCH" | jq -r '.conclusion')
71+
fi
72+
fi
73+
fi
74+
75+
# ── Emit result ─────────────────────────────────────────────────────
76+
jq -n \
77+
--arg version_code "$VERSION_CODE" \
78+
--arg commit_sha "$COMMIT_SHA" \
79+
--arg commit_short "$COMMIT_SHORT" \
80+
--arg commit_message "$COMMIT_MESSAGE" \
81+
--arg commit_date "$COMMIT_DATE" \
82+
--arg total_commits "$TOTAL" \
83+
--arg run_id "${RUN_ID:-}" \
84+
--arg run_url "${RUN_URL:-}" \
85+
--arg run_status "${RUN_STATUS:-}" \
86+
--arg run_conclusion "${RUN_CONCLUSION:-}" \
87+
'{
88+
version_code: ($version_code | tonumber),
89+
commit: {
90+
sha: $commit_sha,
91+
short: $commit_short,
92+
message: $commit_message,
93+
date: $commit_date
94+
},
95+
total_commits: ($total_commits | tonumber),
96+
github_actions: (
97+
if $run_id != "" then {
98+
run_id: ($run_id | tonumber),
99+
url: $run_url,
100+
status: $run_status,
101+
conclusion: $run_conclusion
102+
} else null end
103+
)
104+
}'

.claude/skills/r8-mapping/SKILL.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
name: r8-mapping
3+
description: >
4+
Download and use the R8 mapping file for a Flipcash release build to
5+
deobfuscate stack traces. Usage: /r8-mapping <versionCode>
6+
user-invocable: true
7+
argument-hint: "<versionCode>"
8+
allowed-tools:
9+
- Bash
10+
- Read
11+
- Grep
12+
---
13+
14+
# R8 Mapping: download and deobfuscate
15+
16+
Download the R8 `mapping.txt` for a Flipcash release build and use it to
17+
deobfuscate stack traces.
18+
19+
## Background
20+
21+
Release builds are minified/obfuscated by R8. The mapping file is uploaded as
22+
part of the `release-artifacts` artifact in the "Flipcash2 Build and Deploy"
23+
GitHub Actions workflow (ID `229420296`). The mapping lives at
24+
`mapping/release/mapping.txt` inside the artifact.
25+
26+
**R8 class merging**: R8 may merge multiple unrelated classes into one
27+
obfuscated class. Use line numbers from the stack trace to disambiguate which
28+
original class a frame belongs to.
29+
30+
## Step 1 — Download the mapping
31+
32+
Parse `$ARGUMENTS` for a versionCode (integer). If not provided, ask the user.
33+
34+
```bash
35+
bash .claude/skills/r8-mapping/scripts/r8-mapping.sh <versionCode>
36+
```
37+
38+
The script emits JSON:
39+
40+
```json
41+
{
42+
"mapping_path": "/tmp/r8-mapping-3797/mapping/release/mapping.txt",
43+
"run_id": 26846060877,
44+
"version_code": 3797,
45+
"line_count": 1211075
46+
}
47+
```
48+
49+
## Step 2 — Deobfuscate classes
50+
51+
To find what an obfuscated class name maps to:
52+
53+
```bash
54+
grep " -> <obfuscated_class>:" <mapping_path>
55+
```
56+
57+
This returns lines like:
58+
```
59+
com.original.ClassName -> ag3:
60+
com.other.MergedClass -> ag3:
61+
```
62+
63+
Multiple results means R8 merged those classes. Use stack trace line numbers
64+
to disambiguate — each class section in the mapping contains line-number
65+
ranges for its methods.
66+
67+
## Step 3 — Deobfuscate methods
68+
69+
After finding the class section, look for method mappings within it:
70+
71+
```bash
72+
# Find the class section and its method mappings
73+
grep -A 200 "^com.original.ClassName -> <obfuscated>:" <mapping_path> | head -200
74+
```
75+
76+
Method lines look like:
77+
```
78+
1:5:void run():123:127 -> run
79+
6:10:void otherMethod():45:49 -> a
80+
```
81+
82+
The format is:
83+
```
84+
<obfuscated_line_start>:<obfuscated_line_end>:<return_type> <original_method>(<params>):<original_line_start>:<original_line_end> -> <obfuscated_method>
85+
```
86+
87+
Match the line number from the stack trace against `obfuscated_line_start:obfuscated_line_end`
88+
to find the original method and its original line numbers.
89+
90+
## Output
91+
92+
Present deobfuscated stack frames clearly:
93+
94+
```
95+
Original: com.original.ClassName.methodName (ClassName.kt:127)
96+
Obfuscated: ag3.run (SourceFile:3)
97+
```
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env bash
2+
# r8-mapping.sh — Download the R8 mapping file for a Flipcash release build.
3+
#
4+
# Downloads the `release-artifacts` artifact from the GitHub Actions run
5+
# associated with a given versionCode, then extracts the R8 mapping.txt.
6+
#
7+
# Usage:
8+
# ./r8-mapping.sh <versionCode>
9+
# ./r8-mapping.sh --run-id <github_actions_run_id>
10+
#
11+
# Output: JSON with the path to the downloaded mapping file and metadata.
12+
# The mapping file is saved to /tmp/r8-mapping-<versionCode>/mapping/release/mapping.txt
13+
14+
set -euo pipefail
15+
16+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17+
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
18+
BUILD_LOOKUP="$REPO_ROOT/.claude/skills/build-lookup/scripts/build-lookup.sh"
19+
WORKFLOW_ID="229420296"
20+
21+
# ── Argument parsing ────────────────────────────────────────────────
22+
RUN_ID=""
23+
VERSION_CODE=""
24+
25+
while [[ $# -gt 0 ]]; do
26+
case "$1" in
27+
--run-id) RUN_ID="$2"; shift 2 ;;
28+
*) VERSION_CODE="$1"; shift ;;
29+
esac
30+
done
31+
32+
# ── Resolve run ID if only versionCode given ────────────────────────
33+
if [[ -z "$RUN_ID" && -n "$VERSION_CODE" ]]; then
34+
LOOKUP_JSON=$(bash "$BUILD_LOOKUP" "$VERSION_CODE")
35+
36+
RUN_ID=$(echo "$LOOKUP_JSON" | jq -r '.github_actions.run_id // empty')
37+
COMMIT_SHA=$(echo "$LOOKUP_JSON" | jq -r '.commit.sha')
38+
39+
if [[ -z "$RUN_ID" ]]; then
40+
echo "ERROR: No GitHub Actions run found for versionCode $VERSION_CODE (commit $COMMIT_SHA)" >&2
41+
echo "Try: gh run list --workflow=$WORKFLOW_ID --commit=${COMMIT_SHA:0:9} --limit=10" >&2
42+
exit 1
43+
fi
44+
elif [[ -z "$RUN_ID" ]]; then
45+
echo "Usage: r8-mapping.sh <versionCode>" >&2
46+
echo " r8-mapping.sh --run-id <run_id>" >&2
47+
exit 1
48+
fi
49+
50+
# ── Download artifact ───────────────────────────────────────────────
51+
DEST_DIR="/tmp/r8-mapping-${VERSION_CODE:-$RUN_ID}"
52+
MAPPING_PATH="$DEST_DIR/mapping/release/mapping.txt"
53+
54+
if [[ -f "$MAPPING_PATH" ]]; then
55+
# Already downloaded, skip
56+
LINE_COUNT=$(wc -l < "$MAPPING_PATH" | tr -d ' ')
57+
else
58+
rm -rf "$DEST_DIR"
59+
mkdir -p "$DEST_DIR"
60+
61+
echo "Downloading release-artifacts from run $RUN_ID..." >&2
62+
gh run download "$RUN_ID" \
63+
-n release-artifacts \
64+
-D "$DEST_DIR" 2>&1 >&2
65+
66+
if [[ ! -f "$MAPPING_PATH" ]]; then
67+
# Try alternate path — artifact may have different structure
68+
ALT=$(find "$DEST_DIR" -name "mapping.txt" -type f 2>/dev/null | head -1)
69+
if [[ -n "$ALT" ]]; then
70+
MAPPING_PATH="$ALT"
71+
else
72+
echo "ERROR: mapping.txt not found in downloaded artifacts" >&2
73+
echo "Contents of $DEST_DIR:" >&2
74+
find "$DEST_DIR" -type f >&2
75+
exit 1
76+
fi
77+
fi
78+
79+
LINE_COUNT=$(wc -l < "$MAPPING_PATH" | tr -d ' ')
80+
echo "Downloaded mapping.txt ($LINE_COUNT lines)" >&2
81+
fi
82+
83+
# ── Emit result ─────────────────────────────────────────────────────
84+
jq -n \
85+
--arg mapping_path "$MAPPING_PATH" \
86+
--arg run_id "$RUN_ID" \
87+
--arg version_code "${VERSION_CODE:-unknown}" \
88+
--arg line_count "$LINE_COUNT" \
89+
'{
90+
mapping_path: $mapping_path,
91+
run_id: ($run_id | tonumber),
92+
version_code: (if $version_code != "unknown" then ($version_code | tonumber) else null end),
93+
line_count: ($line_count | tonumber)
94+
}'

0 commit comments

Comments
 (0)