Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions .claude/skills/triage/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
---
name: triage
description: >
Fetch the top open Bugsnag production issue, investigate with evidence from
stack traces / logs / breadcrumbs, propose a fix direction, route through
domain experts, and write a lean review brief.
Triage a Bugsnag production issue — either the top open issue or a specific
issue by URL/ID. Investigate with evidence from stack traces / logs /
breadcrumbs, propose a fix direction, route through domain experts, and write
a lean review brief.
allowed-tools:
- Bash
- Read
Expand All @@ -21,9 +22,19 @@ allowed-tools:
You are a senior Android engineer performing daily Bugsnag triage for the
Flipcash Android app. Follow the steps below exactly.

## Step 1 — Fetch the top open issue
## Step 1 — Fetch the issue

Run the helper script:
If the user provided a Bugsnag URL or error ID, pass it to the helper script:

```bash
# Specific issue by URL
bash .claude/skills/triage/scripts/bugsnag-top.sh --url "https://app.bugsnag.com/org/project/errors/abc123"

# Specific issue by error ID
bash .claude/skills/triage/scripts/bugsnag-top.sh --error-id abc123
```

Otherwise, fetch the top open issue automatically:

```bash
bash .claude/skills/triage/scripts/bugsnag-top.sh
Expand Down
77 changes: 55 additions & 22 deletions .claude/skills/triage/scripts/bugsnag-top.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
# ./bugsnag-top.sh --all # top open error, production, all versions
# ./bugsnag-top.sh --since 2026-05-01T00:00:00Z # custom since filter
# ./bugsnag-top.sh --severity error # filter by severity
# ./bugsnag-top.sh --error-id <id> # fetch a specific error by ID
# ./bugsnag-top.sh --url <bugsnag_url> # fetch a specific error by Bugsnag URL

set -euo pipefail

Expand Down Expand Up @@ -63,13 +65,25 @@ RELEASE_STAGE="production"
SEVERITY=""
SINCE=""
ALL_VERSIONS=false
EXPLICIT_ERROR_ID=""

while [[ $# -gt 0 ]]; do
case "$1" in
--internal) RELEASE_STAGE="production"; SINCE=""; ALL_VERSIONS=true; shift ;;
--severity) SEVERITY="$2"; shift 2 ;;
--all) ALL_VERSIONS=true; shift ;;
--since) SINCE="$2"; shift 2 ;;
--error-id) EXPLICIT_ERROR_ID="$2"; shift 2 ;;
--url)
# Extract error ID from a Bugsnag dashboard URL
# e.g. https://app.bugsnag.com/org/project/errors/abc123def456
EXPLICIT_ERROR_ID=$(echo "$2" | grep -oE '/errors/([a-f0-9]+)' | sed 's|/errors/||')
if [[ -z "$EXPLICIT_ERROR_ID" ]]; then
echo "ERROR: Could not extract error ID from URL: $2" >&2
exit 1
fi
shift 2
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
Expand Down Expand Up @@ -112,30 +126,49 @@ api() {
return 1
}

# ── Fetch top open error ──────────────────────────────────────────────
FILTERS="filters[error.status][]=open&filters[app.release_stage][]=${RELEASE_STAGE}"
if [[ -n "$SEVERITY" ]]; then
FILTERS="${FILTERS}&filters[event.severity][]=${SEVERITY}"
fi
if [[ -n "$SINCE" ]]; then
FILTERS="${FILTERS}&filters[event.since][]=${SINCE}"
fi

ERRORS_URL="${API_BASE}/projects/${PROJECT_ID}/errors?${FILTERS}&sort=events&direction=desc&per_page=1"
ERRORS_JSON=$(api "$ERRORS_URL")

if [[ -z "$ERRORS_JSON" ]] || ! echo "$ERRORS_JSON" | jq -e '.[0]' >/dev/null 2>&1; then
echo "No open errors found for release_stage=${RELEASE_STAGE}" >&2
exit 1
# ── Fetch error ───────────────────────────────────────────────────────
if [[ -n "$EXPLICIT_ERROR_ID" ]]; then
# Fetch a specific error by ID
ERROR_URL="${API_BASE}/projects/${PROJECT_ID}/errors/${EXPLICIT_ERROR_ID}"
ERROR_JSON=$(api "$ERROR_URL")

if [[ -z "$ERROR_JSON" ]] || ! echo "$ERROR_JSON" | jq -e '.id' >/dev/null 2>&1; then
echo "ERROR: Could not fetch error ${EXPLICIT_ERROR_ID}" >&2
exit 1
fi

ERROR_ID="$EXPLICIT_ERROR_ID"
TITLE=$(echo "$ERROR_JSON" | jq -r '(.error_class // "") + ": " + (.message // "")')
SEVERITY_VAL=$(echo "$ERROR_JSON" | jq -r '.severity')
EVENTS=$(echo "$ERROR_JSON" | jq -r '.events')
USERS=$(echo "$ERROR_JSON" | jq -r '.users')
FIRST_SEEN=$(echo "$ERROR_JSON" | jq -r '.first_seen')
else
# Fetch top open error
FILTERS="filters[error.status][]=open&filters[app.release_stage][]=${RELEASE_STAGE}"
if [[ -n "$SEVERITY" ]]; then
FILTERS="${FILTERS}&filters[event.severity][]=${SEVERITY}"
fi
if [[ -n "$SINCE" ]]; then
FILTERS="${FILTERS}&filters[event.since][]=${SINCE}"
fi

ERRORS_URL="${API_BASE}/projects/${PROJECT_ID}/errors?${FILTERS}&sort=events&direction=desc&per_page=1"
ERRORS_JSON=$(api "$ERRORS_URL")

if [[ -z "$ERRORS_JSON" ]] || ! echo "$ERRORS_JSON" | jq -e '.[0]' >/dev/null 2>&1; then
echo "No open errors found for release_stage=${RELEASE_STAGE}" >&2
exit 1
fi

ERROR_ID=$(echo "$ERRORS_JSON" | jq -r '.[0].id')
TITLE=$(echo "$ERRORS_JSON" | jq -r '.[0] | (.error_class // "") + ": " + (.message // "")')
SEVERITY_VAL=$(echo "$ERRORS_JSON" | jq -r '.[0].severity')
EVENTS=$(echo "$ERRORS_JSON" | jq -r '.[0].events')
USERS=$(echo "$ERRORS_JSON" | jq -r '.[0].users')
FIRST_SEEN=$(echo "$ERRORS_JSON" | jq -r '.[0].first_seen')
fi

ERROR_ID=$(echo "$ERRORS_JSON" | jq -r '.[0].id')
TITLE=$(echo "$ERRORS_JSON" | jq -r '.[0] | (.error_class // "") + ": " + (.message // "")')
SEVERITY_VAL=$(echo "$ERRORS_JSON" | jq -r '.[0].severity')
EVENTS=$(echo "$ERRORS_JSON" | jq -r '.[0].events')
USERS=$(echo "$ERRORS_JSON" | jq -r '.[0].users')
FIRST_SEEN=$(echo "$ERRORS_JSON" | jq -r '.[0].first_seen')

# ── Fetch latest event for this error ─────────────────────────────────
EVENTS_URL="${API_BASE}/projects/${PROJECT_ID}/errors/${ERROR_ID}/events?sort=timestamp&direction=desc&per_page=1"
EVENTS_JSON=$(api "$EVENTS_URL")
Expand Down
Loading