---
name: garmin-era
description: Pull and read Connect IQ crash reports (ERA - Error Reporting and Analytics) for published Garmin apps from the command line, resolve app UUIDs, and decode the backtraces and device part numbers. Use when the user asks about ERA, Garmin crash reports, why a published watch face or app crashes on real watches, or wants to check field crashes for a released Connect IQ app.
---

# Garmin ERA from the command line

ERA is Garmin's crash reporting for published Connect IQ apps. It is usually
opened from the VS Code extension, but the SDK ships a **CLI** that does the
same download, which works when the IDE integration does not.

Works for any published Connect IQ app the signed-in developer account owns
(watch faces, data fields, widgets, device apps).

## The tool

```bash
sdk="$(tr -d '\n' < ~/Library/Application\ Support/Garmin/ConnectIQ/current-sdk.cfg)"
"$sdk/bin/era" -a <full-app-uuid>
```

It prints a **JSON array to stdout** and writes no files. `-h` is the only
other flag. Each array element is one grouped error.

## Prerequisites, in the order they bite

**1. The `location` key.** A fresh install has no `location` in
`~/Library/Application Support/Garmin/ConnectIQ/sdkmanager-config.ini`, and
without it `era` dies before doing anything:

```
java.lang.NumberFormatException: Cannot parse null string
    at ...CredentialsHandler.getServerFromConfigFile
```

Fix by appending one line (back the file up first):

```bash
printf '\nlocation=0\n' >> ~/Library/Application\ Support/Garmin/ConnectIQ/sdkmanager-config.ini
```

`0` = `Server.PRODUCTION` (1 = STAGE, 2 = TEST).

**2. The login, and it is per ACCOUNT.** Credentials come from the macOS
keychain under service `Garmin.ConnectIQ.SdkManager`, written by the Connect IQ
SDK Manager app sign-in. `era` has no login command of its own. Never read the
keychain entry and never sign in on the user's behalf - if the token is missing
or stale, ask them to open the SDK Manager and sign in with the account that
owns the apps.

The machine holds exactly **one signed-in account at a time**, and it must be
the account the app belongs to. An app published under a different developer
account - an employer's, say - answers `You are not authorized to download
crash report for the app` even though the id is correct and the login is
healthy (verified 08/2026 against a personal account and an employer's app).
Compare `developerId` from `/apps/<uuid>` against the account you are signed in
as before assuming a broken token.

This is **not** the developer key: the key that signs builds has no bearing
on ERA access.

**3. The token is rewritten on every run, and the write can fail silently.**
`era` renews its token each time it runs and writes the new one back to the
keychain. **From a sandboxed shell that write fails without an error**, so the
first call succeeds on the token it read and every later call answers `You are
not authorized to download crash report for the app`. It reads exactly like a
wrong account. Run `era` from an ordinary terminal. Signing in again in the SDK
Manager buys exactly one more call, which is the tell.

Read the errors literally, they distinguish the failure modes:

| Message | Meaning |
|---|---|
| `The app was not found. Please check the app id.` | Bad or truncated UUID. Auth is fine. |
| `You are not authorized to download crash report for the app.` | Auth reached the server and was rejected. Wrong account, or a token the shell could not write back (3). |
| `[]` | Authorized, no reports. This is success. |

If the token cannot be refreshed, check that the SDK Manager is actually
installed: `sdkmanager-location.cfg` often still points at the install DMG
(`/Volumes/Connect IQ SDK Manager/SdkManager.app/`), which is not mounted, and
then nothing on the machine can renew the token - which also breaks VS Code.

## Finding the app UUID

`-a` needs the **full** UUID. Notes and tables usually record only the first 8
characters, and the API rejects those (`418 ... is an invalid app UUID`).

The open store API needs no auth. Get the developer id from any known app,
then list everything that developer publishes:

```bash
API=https://apps.garmin.com/api/appsLibraryExternalServices/api/asw
curl -s "$API/apps/<known-full-uuid>" | python3 -c "import sys,json;print(json.load(sys.stdin)['developerId'])"
curl -s "$API/apps/developer/<devId>?startPageIndex=0&pageSize=25"
```

Paging quirks, all of which return an error rather than a default:
`startPageIndex` is required; `pageSize` accepts 20 or 25 but **rejects 50**
(`Invalid pageSize!`). Keyword search is `/apps/keywords?keywords=<term>` with
the same two params. The endpoint rate-limits to an empty `[]` after ~15 rapid
calls, so sleep ~1 s between calls and cache the id list.

There is no top-level `name` on either response - the readable name lives in
`appLocalizations[].name` on the single-app endpoint (`/apps/<uuid>`), next to
`latestExternalVersion`, `downloadCount`, `averageRating` and
`supportEmailAddress`. The list endpoint returns ids only, so resolve a name by
fetching the app itself, or match ids against your own notes.

## Reading a report

```bash
"$sdk/bin/era" -a <uuid> | python3 ~/.claude/skills/garmin-era/scripts/era_report.py
```

That script pretty-prints each group and resolves the device part numbers.
Fields that matter:

- `occurrenceCount` - how many real users hit it. Rank by this.
- `externalAppVersions` / `appVersions` - store version and internal build.
  **Only reports matching the currently published build are actionable.**
- `products[].product` - a part number like `006-B4125-00`, not a device name.
  Resolve against `worldWidePartNumber` / `hardwarePartNumber` in
  `~/Library/Application Support/Garmin/ConnectIQ/Devices/*/compiler.json`.
  One number can map to several devices (rebadges share hardware).
- `translatedBacktrace` - already resolved to file, line and symbol, using the
  **absolute paths of the machine that built the upload**. No debug xml needed.

### Backtrace caveats

- **Frame order is approximate** under `-O 3p`. Inlined and optimized frames
  can appear out of sequence, and a line can land a few lines off inside the
  right function. Trust the *function chain*, not the exact line.
- **Line numbers belong to the revision that built the published upload**, not
  to HEAD. Before matching, check whether the files changed since that release
  (`git log --since=<release date> -- <file>`).
- Groups that differ only in the top frame's line number are usually one bug
  reached down two paths - read them together.

## Turning a report into a reproduction

The value of ERA is the exact frame chain. Use it to force the same state in
the simulator rather than guessing:

1. Reproduce, in the sim, on a device from the `products` list.
2. Confirm the sim's `CIQ_LOG.YML` (in `$TMPDIR/com.garmin.connectiq/GARMIN/APPS/LOGS/`)
   shows the **same function chain** as ERA. That is the proof you reproduced
   the field bug and not a lookalike.
3. Fix, then re-run the same forcing steps and confirm the log stays empty.

Temporary probes that force an ordering or a failure (calling a handler early,
stubbing an API to return empty) are the fastest way in. Revert every probe and
grep for its marker before committing.

## What ERA does not tell you

Only **unhandled exceptions and out-of-memory kills** are reported. Silent
misbehaviour - a wrong value written to a property, a setting that will not
stick, a field rendering blank - produces no report at all. A low ERA count is
never evidence that few users are affected; pair it with support mail and store
reviews before drawing conclusions about impact.
