Files
openhuman/docs/sentry.md
T

221 lines
11 KiB
Markdown

# Sentry Release Tracking & Source Maps
_Tracks issue [#405](https://github.com/tinyhumansai/openhuman/issues/405)._
OpenHuman reports crashes and errors from three surfaces, each with its own
Sentry project but all sharing the **same release tag** so events line up:
- **Frontend** — `@sentry/react` in `app/src/services/analytics.ts`
`openhuman-react`.
- **Rust core (CLI / Docker)** — `sentry::init` in `src/main.rs`
`openhuman-core`.
- **Tauri shell (desktop)** — `sentry::init` in
`app/src-tauri/src/lib.rs::run()``openhuman-tauri`. The core is
linked into this binary as a path dep, so a single `cargo tauri build`
produces all the Rust DIFs uploaded for both projects.
## Canonical release identifier
Both surfaces report the **same** release tag:
```
openhuman@<semver>+<short_git_sha>
```
Where:
- `<semver>` is `packageJson.version` / `env!("CARGO_PKG_VERSION")`.
- `<short_git_sha>` is the first 12 chars of the commit that produced the
build. When the SHA is absent (local dev), the tag collapses to
`openhuman@<semver>` with no `+` suffix.
The frontend computes this in `app/src/utils/config.ts::SENTRY_RELEASE`
from `VITE_BUILD_SHA`. The core does the same in
`src/main.rs::build_release_tag()` from `option_env!("OPENHUMAN_BUILD_SHA")`.
## Environments
Reported as the Sentry `environment` tag:
| Value | When |
| ------------- | --------------------------------------------------------------- |
| `development` | Local `pnpm tauri dev` / debug builds |
| `staging` | `VITE_OPENHUMAN_APP_ENV=staging` or `OPENHUMAN_APP_ENV=staging` |
| `production` | Release builds from `workflow_dispatch` with `build_target=production` |
Fallback precedence for the core:
1. `OPENHUMAN_APP_ENV` env var at runtime (override).
2. Compile-time `debug_assertions``development`.
3. Otherwise → `production`.
## Source-map upload
The frontend emits source maps (`vite.config.ts` sets `build.sourcemap =
true`). When `SENTRY_AUTH_TOKEN` is present at build time
`@sentry/vite-plugin`:
1. Uploads every `dist/**/*.js` and its `.map` sibling.
2. Tags the upload with the canonical release name above.
3. **Deletes the on-disk `.map` files after upload** so users never receive
them in the shipped bundle.
If `SENTRY_AUTH_TOKEN` is empty (local dev, smoke CI, forks without
secrets), the plugin registers as a no-op — the build still produces source
maps on disk but nothing is uploaded. This keeps the local dev loop zero-
config.
## Rust debug symbols + source context
`scripts/upload_sentry_symbols.sh` runs after the Tauri build in
`release.yml` and pushes:
- **Debug info files** (`.dwp` / `.debug` / `.pdb` / macOS `.dSYM`) found
under `app/src-tauri/target/<triple>/release/deps`. The
`[profile.release] debug = "line-tables-only"` setting in both
`Cargo.toml` files emits just enough DWARF (file+line tables, no full
type info) for Sentry to symbolicate frame addresses without bloating
the shipped binary. `split-debuginfo = "packed"` writes the debug data
into a separate `.dSYM` bundle on macOS.
- **A `.src.zip` source bundle** built from the Rust source files
referenced by those DIFs (`sentry-cli upload-dif --include-sources`).
This is what lets Sentry render the surrounding lines of source for a
panic, not just `function_name + 0xNNN`. Without it, the event detail
page shows a symbolicated stack with empty source context.
For Sentry to actually walk the loaded shared libraries at runtime and
attach each image's debug-id to events, the `sentry` crate is built with
the `debug-images` feature in both `Cargo.toml` files. This registers
`DebugImagesIntegration` as part of the default integration set — events
arrive with `debug_meta.images` populated, and Sentry's symbolicator
resolves those debug-ids against uploaded DIFs to attach `pre_context` /
`context_line` / `post_context` to each frame.
The script drives the per-project release lifecycle for the project it's
called against:
1. `sentry-cli releases new "$SENTRY_RELEASE"` — creates / no-ops the release.
2. `sentry-cli releases set-commits --auto --ignore-missing` — associates
commits using the GitHub-provided range. `--ignore-missing` keeps shallow
CI checkouts from failing.
3. `sentry-cli upload-dif --include-sources` — DIFs + `.src.zip`.
4. `sentry-cli releases finalize "$SENTRY_RELEASE"` — marks the release
complete (used by Sentry to compute "regression" / "new in release").
`releases new`, `set-commits`, and `finalize` are idempotent — re-running
on the same SHA reuses the existing release and DIFs are deduplicated by
debug-ID. The deploy marker is **not** in this script — it lives in
`release.yml`'s "Record Sentry deploy marker" step, which fires once per
matrix target after the upload step. `sentry-cli releases deploys ... new`
does not deduplicate by (release, env), so re-running CI for the same
release intentionally adds another deploy row representing a separate
deploy attempt.
## CI configuration
`release.yml` + `release-packages.yml` thread the following through to the
build steps. Any subset can be set on a per-environment basis in the
`Production` / `Staging` GitHub Actions environment:
### Required for upload to work
| Name | Type | Scope | Purpose |
| ------------------------------------- | -------- | ---------------------- | ------------------------------------------------------ |
| `secrets.SENTRY_AUTH_TOKEN` | secret | build-desktop | Auth for `@sentry/vite-plugin` + `sentry-cli` |
| `vars.SENTRY_ORG` | variable | build-desktop | Sentry org slug |
| `vars.SENTRY_PROJECT_REACT` | variable | build-desktop (Vite) | Project slug for the frontend bundle + source maps |
| `vars.SENTRY_PROJECT_CORE` | variable | symbols-upload | Project slug for the Rust DIFs + source bundle |
| `vars.SENTRY_PROJECT_TAURI` | variable | (reserved) | Reserved for the Tauri shell when symbol-uploads split |
| `vars.OPENHUMAN_REACT_SENTRY_DSN` | variable | build-desktop (Vite) | Frontend DSN (baked by Vite define) |
| `vars.OPENHUMAN_CORE_SENTRY_DSN` | variable | build-desktop (Rust) | Core sidecar DSN (baked via `option_env!`) |
| `vars.OPENHUMAN_TAURI_SENTRY_DSN` | variable | build-desktop (Tauri) | Tauri shell DSN (baked via `option_env!`) |
The legacy `vars.OPENHUMAN_SENTRY_DSN`, `vars.VITE_SENTRY_DSN`,
`vars.SENTRY_PROJECT`, and `vars.SENTRY_PROJECT_FRONTEND` are no longer
read by `release.yml` — they were superseded by the per-surface variables
above as part of #1032. Safe to delete from any configured GitHub Actions
environment that still has them set.
### Provided automatically
| Name | Source |
| ------------------------ | ------------------------------------------------------------------- |
| `VITE_BUILD_SHA` | `needs.prepare-build.outputs.sha` (tag commit, full 40 chars) |
| `OPENHUMAN_BUILD_SHA` | Same — passed to `cargo build` for the Rust core / Tauri shell |
| `SENTRY_RELEASE` | `openhuman@<version>+<short_sha>``short_sha` is `sha[:12]`, matches the truncation `config.ts` / `vite.config.ts` / `main.rs` / `app/src-tauri/src/lib.rs` apply at runtime. Same value on Vite, symbols upload, and the deploy-marker steps |
| `SENTRY_ENVIRONMENT` | `staging` / `production` from the workflow's `build_target` — only consumed by the deploy-marker step |
### Personal Sentry DSN (local)
Drop the DSN into your repo-local `.env`:
```sh
# .env
OPENHUMAN_SENTRY_DSN=https://<key>@o<org>.ingest.sentry.io/<project>
```
`src/main.rs` now loads `.env` **before** `sentry::init`, so the runtime
env var is visible to the client at startup without needing a manual
`source scripts/load-dotenv.sh`.
For the frontend, put `VITE_SENTRY_DSN` in `app/.env.local`.
## Verification runbook
1. **Event arrives**. Trigger a test event from the core CLI:
```sh
./target/release/openhuman-core sentry-test
# or on an installed release (Windows):
# "%LOCALAPPDATA%\Programs\OpenHuman\OpenHuman.exe" core sentry-test
# or (macOS):
# /Applications/OpenHuman.app/Contents/MacOS/openhuman-core-* sentry-test
```
The command prints an event UUID on success; search it in the Sentry
dashboard.
2. **Release tag is right**. On the event detail page, the `Release` field
should read `openhuman@<version>+<short_sha>` (matching the tag that cut
the release).
3. **Environment tag is right**. Production CI dispatch → `production`.
Staging dispatch → `staging`. Local `pnpm tauri dev` → `development`.
4. **Stack traces are symbolicated**. Force a frontend error from the
installed app; the event's stack trace should show original
TypeScript file names and line numbers (not hashed `assets/index-*.js`).
5. **CI failure is loud when misconfigured**. If `SENTRY_AUTH_TOKEN` is
missing and the release is supposed to upload source maps, the CI run
will warn in the Vite build log rather than silently producing an
un-symbolicated release.
## Troubleshooting
- **Events arrive without a release tag** — check the Vite build log for
`SENTRY_RELEASE`; if empty, the CI workflow didn't pass it through.
- **Events arrive without symbolication** — open the release in Sentry →
"Source Maps" tab. Missing artifacts mean either `SENTRY_AUTH_TOKEN` was
empty, or the plugin ran but the `assets:` glob didn't match (inspect the
upload summary printed during `pnpm build`).
- **Frontend and core show different releases** — verify
`needs.prepare-build.outputs.sha` is identical between the core build
step (`OPENHUMAN_BUILD_SHA`) and the frontend build step
(`VITE_BUILD_SHA` / `SENTRY_RELEASE`).
- **No events from a release build, only from local** — `vars.*` probably
isn't defined on the `Production` environment. Set it and re-cut the
release.
- **Rust frames show function name but no source** — the `.src.zip` for
this release didn't upload, OR the `debug-images` integration isn't
active. Check the "Upload core sidecar debug symbols to Sentry" workflow
log for `Bundled N source files`; absence means `--include-sources`
didn't take effect or DWARF wasn't emitted (verify the
`[profile.release] debug = "line-tables-only"` block in `Cargo.toml`).
If the bundle uploaded but events still render blank, confirm the
`sentry` crate has the `debug-images` feature enabled in both
`Cargo.toml` files.
- **DIFs uploaded but events still report a release with no artifacts**
— verify `SENTRY_RELEASE` was set to `openhuman@<version>+<short_sha>`
in all three places that construct it (Vite build step, symbols-upload
step, deploy-marker step). All three must reference
`needs.prepare-build.outputs.short_sha`, not the full `sha`.
- **No deploy marker on the release page** — confirm the dedicated
"Record Sentry deploy marker" step ran and `SENTRY_ENVIRONMENT`
resolved to a non-empty value (`release.yml` derives it from
`inputs.build_target`).