fix(sentry): auto-send React events; collapse core→tauri for desktop#1086
Conversation
…ansai#405) Five small additive changes that finish the source-context work for tinyhumansai#405 on top of tinyhumansai#1032's per-surface project split. 1. **`debug-images` feature enabled** in the `sentry` crate for both the core (`Cargo.toml`) and the Tauri shell (`app/src-tauri/Cargo.toml`). Registers `DebugImagesIntegration` in the default integration set, so events arrive with `debug_meta.images` populated and Sentry's symbolicator can match the uploaded DIFs to attach `pre_context` / `context_line` / `post_context` to each frame. Without this, panics render with bare `function_name + 0xNNN` even when DIFs uploaded successfully. 2. **`[profile.release]` set to emit DWARF** in both Cargo manifests: `debug = "line-tables-only"` (file+line tables, no full type info) and `split-debuginfo = "packed"` (writes a `.dSYM` bundle on macOS so the shipped binary stays slim). Cargo's default `debug = false` for release left binaries with no DWARF at all — `sentry-cli upload-dif --include-sources` (already on main from tinyhumansai#1032) had nothing to ship. 3. **`SENTRY_RELEASE` aligned with runtime truncation.** `config.ts`, `vite.config.ts`, `main.rs`, and `app/src-tauri/src/lib.rs` all slice `VITE_BUILD_SHA` / `OPENHUMAN_BUILD_SHA` to 12 chars when computing the release tag at runtime, but `release.yml` was passing the full 40-char SHA into `SENTRY_RELEASE` for both the Vite build and the symbols-upload step. The Vite plugin and `sentry-cli` use `SENTRY_RELEASE` raw, so artifacts attached to `openhuman@<v>+<40char>` while events emitted `openhuman@<v>+<12char>` — different releases, no symbolication. Added a `short_sha` output to `prepare-build` (sliced to 12 chars in the resolve step) and use it in both `SENTRY_RELEASE` constructions. 4. **Per-release Sentry deploy marker.** New `Record Sentry deploy marker` step in `release.yml` that runs once per matrix target after the upload step, calling `sentry-cli releases deploys ... new` with `SENTRY_ENVIRONMENT` derived from `inputs.build_target`. Closes tinyhumansai#405's "release page links commits → deploys" criterion. Kept out of `upload_sentry_symbols.sh` to avoid duplicate markers when the script is invoked multiple times in a release lifecycle — `sentry-cli releases deploys new` does NOT deduplicate by (release, env), so re-running CI for the same release adds a new row each time. 5. **`sentry-cli 3.x` log-level compat.** Changed `--log-level=warning` to `--log-level=warn` in `upload_sentry_symbols.sh`. The full word was rejected as `invalid value '...' for '--log-level'` on 3.x and the script silently skipped uploads (logged "Some debug symbols may have failed to upload" but no DIFs were actually pushed). Plus housekeeping: - `scripts/ci-secrets.example.json` updated to the per-surface vars `release.yml` actually reads now (`SENTRY_PROJECT_REACT/CORE/TAURI`, `OPENHUMAN_REACT/CORE/TAURI_SENTRY_DSN`). Legacy `OPENHUMAN_SENTRY_DSN` / `VITE_SENTRY_DSN` / `SENTRY_PROJECT(_FRONTEND)` keys removed (no longer read since tinyhumansai#1032). - `docs/sentry.md` rewrites the surface list, adds a Rust source-context section explaining `debug-images` + the DWARF profile + the upload lifecycle, updates the required-vars table to the per-surface set, and adds troubleshooting entries for the new failure modes. Cargo.lock files intentionally not modified — the new `debug-images` feature pulls `findshlibs` transitively, which cargo will resolve into the lock files on the first build. CI's `cargo build` regenerates them in place. Closes tinyhumansai#405. Supersedes tinyhumansai#973 (which was rebased through merging a stale branch and ended up with diff noise unrelated to the actual fix).
…yhumansai#405) `release-staging.yml` (added in tinyhumansai#1066) was cutting staging desktop bundles with **zero** Sentry instrumentation: no `environment:` declaration on its jobs, no `VITE_SENTRY_DSN` / `OPENHUMAN_TAURI_SENTRY_DSN` threaded into the Tauri build step, no `SENTRY_AUTH_TOKEN` for source-map upload, and no symbol-upload / deploy-marker steps. Net effect: the staging build's `Sentry.init` short-circuits in `app/src/services/analytics.ts` (`if (!SENTRY_DSN) return;`), and the Rust core / Tauri shell `option_env!` lookups resolve to empty — so the shipped staging app reports nothing to Sentry, ever. This mirrors the existing wiring on `release.yml`'s build-desktop path: - Both `prepare-build` and `build-desktop` declare `environment: Production` so the same `vars.OPENHUMAN_REACT_SENTRY_DSN` / `OPENHUMAN_TAURI_SENTRY_DSN` / `SENTRY_PROJECT_REACT/CORE` / `SENTRY_ORG` and `secrets.SENTRY_AUTH_TOKEN` already provisioned for production resolve here. Staging builds tag events with `environment: staging` at runtime (set by `APP_ENVIRONMENT` in `config.ts` and `resolve_sentry_environment()` in the Rust shell), so Sentry can filter them without separate projects. - `prepare-build` exposes a `short_sha` output (first 12 chars of `sha`) for the canonical release tag. Matches the runtime truncation in `config.ts` / `vite.config.ts` / `main.rs` / `app/src-tauri/src/lib.rs` so uploaded source maps + DIFs attach to the same release events emit. - `Build and package Tauri app` step now passes `VITE_SENTRY_DSN`, `OPENHUMAN_TAURI_SENTRY_DSN`, `OPENHUMAN_BUILD_SHA`, `VITE_BUILD_SHA`, `SENTRY_RELEASE` (with `short_sha`), `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, `SENTRY_PROJECT` so `@sentry/vite-plugin` can upload source maps for `openhuman-react` and the Tauri shell binary has its DSN baked in. - New `Upload core sidecar debug symbols to Sentry` step reads from `app/src-tauri/target/<triple>/debug/deps` (not `release/deps` as on production) since staging builds with `--debug`. - New `Record Sentry deploy marker` step fires once per matrix target with `SENTRY_ENVIRONMENT=staging`, satisfying the same release-page link as production. Closes the staging half of tinyhumansai#405's "every production/staging event includes a valid release and environment" criterion.
…er, no error swallowing Three fixes from CodeRabbit's review on tinyhumansai#1067: 1. **Deploy marker fires once per release, not once per matrix target.** The "Record Sentry deploy marker" step lived inside `build-desktop` in both workflows, so the matrix (3 platforms on prod, 4 on staging) produced 3-4 deploy rows per release instead of 1. `sentry-cli releases deploys ... new` does NOT deduplicate by (release, env), so each call really did add a new row. Moved the marker into a dedicated `record-sentry-deploy` job that `needs: [prepare-build, build-desktop]` and runs on a single `ubuntu-latest` runner — exactly one row per release. 2. **Stopped swallowing deploy-marker failures.** Both workflows had a trailing `|| { echo "[WARN] deploy marker failed (non-fatal)"; }` that turned auth/org/release misconfig into a silent green build — exactly the "fail clearly on misconfig" anti-pattern tinyhumansai#405's acceptance criteria warn against. With `set -euo pipefail` the sentry-cli call now fails the job loudly. 3. **release-staging.yml also uploads root target's DIFs.** Staging builds both the Tauri shell (`app/src-tauri/target/<triple>/debug`) AND a standalone `openhuman-core` CLI binary (`target/<triple>/debug`) that's published as a separate artifact for operators. The upload step previously only scanned the shell's target dir, so crashes from the standalone CLI couldn't symbolicate. Now loops over both. Symbols are debug-ID-keyed so upload from both binaries to the same Sentry project doesn't collide. The fourth review comment (template `OPENHUMAN_CORE_SENTRY_DSN` vs runtime `OPENHUMAN_SENTRY_DSN`) is correct as a runtime/template gap in isolation, but `release.yml:484` already aliases `OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_CORE_SENTRY_DSN }}` — the template lists GH Actions var names (matching workflow consumption), not runtime env var names, so the existing template is correct for its purpose. Replied on the comment.
…sktop CodeRabbit caught a real ordering bug on tinyhumansai#1067: the `record-sentry-deploy` job depended on `build-desktop` directly, so it could run before `build-docker` / `publish-updater-manifest` finished. If one of those later phases failed, `cleanup-failed-release` would delete the GitHub release/tag, but Sentry would already have a deploy row for a release that never shipped. Hang off `publish-release` instead. That job already requires the full matrix + docker + updater manifest to succeed, so the deploy marker transitively waits for all of them and only fires once the release is actually published. `record-sentry-deploy` is unchanged on `release-staging.yml` because that workflow has no `publish-release` equivalent — its only build phase is `build-desktop`, so the existing `needs: [prepare-build, build-desktop]` is already correct.
- React: drop the `beforeSend` opt-in queue and auto-forward sanitized events to `openhuman-react`. PII / breadcrumbs / request bodies / frame-level locals + source snippets are still stripped, and the `isAnalyticsEnabled()` consent check now gates `beforeSend`. Tag every event with `surface: "react"`. Add a `VITE_SENTRY_SMOKE_TEST` one-shot trigger for verifying the pipeline end-to-end. - Delete `errorReportQueue` + `ErrorReportNotification` (the user-opt-in scaffolding) and the OAuth stale-app-version `enqueueError` call — replaced with a direct `Sentry.captureMessage` plus `console.warn`. - Workflow: since tinyhumansai#1061 the core lives in-process inside the Tauri shell binary (one process → one `sentry::init` → one hub), so all Rust events from the desktop build route to `openhuman-tauri`. Repoint the Rust DIF upload step in `release.yml` and the Tauri-shell upload in `release-staging.yml` to `vars.SENTRY_PROJECT_TAURI`. Standalone `openhuman-core` CLI binary (built by the staging sidecar step and by `release-packages.yml`) still uploads to `vars.SENTRY_PROJECT_CORE`.
📝 WalkthroughWalkthroughRefactors error reporting from a queue-based, user-review pipeline to consent-gated auto-send via Sentry (sanitizing events in Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant React as React Component
participant ErrorBoundary as Sentry.ErrorBoundary
participant SentrySDK as Sentry SDK
participant beforeSend as beforeSend Hook
participant SentryBackend as Sentry Backend
Note over React,SentryBackend: New Flow: Direct Auto-Send with Consent Gating
React->>ErrorBoundary: Error thrown
ErrorBoundary->>SentrySDK: Capture exception
SentrySDK->>beforeSend: Event for processing
beforeSend->>beforeSend: Check analytics consent
alt Consent Enabled
beforeSend->>beforeSend: Sanitize event (clear breadcrumbs,<br/>remove request/extra, trim contexts/vars)
beforeSend->>SentrySDK: Return sanitized event
SentrySDK->>SentryBackend: Upload event
else Consent Disabled
beforeSend->>SentrySDK: Return null (drop event)
end
sequenceDiagram
autonumber
participant CI as GitHub Actions
participant Build as Rust/Tauri Build
participant Upload as Sentry Upload Step
participant Sentry as Sentry Projects
Note over CI,Sentry: CI symbol upload split per-surface
CI->>Build: Build Tauri shell (includes core)
CI->>Upload: Upload Tauri debug symbols -> `SENTRY_PROJECT_TAURI`
CI->>Upload: Upload core CLI debug symbols -> `SENTRY_PROJECT_CORE`
Upload->>Sentry: DIFs ingested per project with release tag
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Review rate limit: 4/5 reviews remaining, refill in 12 minutes. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/release.yml (1)
551-557:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUpload the whole Tauri release target, not just
release/deps.The comment above says the shell target dir is scanned recursively, but this step only hands
upload_sentry_symbols.shapp/src-tauri/target/${MATRIX_TARGET}/release/deps. That can skip the shell binary’s own debug files, which is where the linked-in core frames now live after the sidecar collapse, so production desktop Rust crashes can still come through unsymbolicated. This also diverges from.github/workflows/release-staging.yml:366-369, which scans the target dir root.Suggested fix
- deps_dir="app/src-tauri/target/${MATRIX_TARGET}/release/deps" - if [ -d "$deps_dir" ]; then - echo "==> Uploading symbols from $deps_dir to ${SENTRY_PROJECT}" - bash scripts/upload_sentry_symbols.sh "$VERSION" "$deps_dir" + dif_dir="app/src-tauri/target/${MATRIX_TARGET}/release" + if [ -d "$dif_dir" ]; then + echo "==> Uploading symbols from $dif_dir to ${SENTRY_PROJECT}" + bash scripts/upload_sentry_symbols.sh "$VERSION" "$dif_dir" else - echo "==> Skipping $deps_dir (not present)" + echo "==> Skipping $dif_dir (not present)" fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 551 - 557, The workflow currently sets deps_dir to "app/src-tauri/target/${MATRIX_TARGET}/release/deps", which misses the shell binary debug files; change the directory passed to scripts/upload_sentry_symbols.sh to the Tauri release target root (e.g., set deps_dir to "app/src-tauri/target/${MATRIX_TARGET}/release" or a new variable like target_dir) so the upload script scans recursively and includes the shell binary and linked core frames; keep the existence check and the call to bash scripts/upload_sentry_symbols.sh "$VERSION" "$target_dir" (preserving MATRIX_TARGET and VERSION usage).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/release-staging.yml:
- Around line 374-394: The build uploads Sentry symbols with a release tag
containing the short SHA, but the standalone CLI binary omits the short SHA
unless OPENHUMAN_BUILD_SHA is set at compile time (see build_release_tag() in
src/main.rs); modify the "Build sidecar core binary" step to export
OPENHUMAN_BUILD_SHA using needs.prepare-build.outputs.short_sha
(OPENHUMAN_BUILD_SHA: ${{ needs.prepare-build.outputs.short_sha }}) so the
compiled binary’s release tag matches the Sentry-uploaded symbols.
In `@app/src/services/analytics.ts`:
- Around line 53-55: beforeSend currently drops all events when
isAnalyticsEnabled() is false which suppresses the one-shot smoke test sent from
initSentry (Sentry.captureMessage('react-sentry-smoke-test', ...)); update
beforeSend in analytics.ts to allow this specific synthetic event through (check
event.message === 'react-sentry-smoke-test' or event.tags?.smoke_test) even when
isAnalyticsEnabled() is false, or alternatively move the Sentry.captureMessage
call in initSentry to fire only after analytics consent is resolved; reference
beforeSend, initSentry, Sentry.captureMessage, isAnalyticsEnabled and
VITE_SENTRY_SMOKE_TEST when making the change.
- Line 108: Replace the direct import.meta.env access in analytics.ts with a
config export from app/src/utils/config.ts: add a boolean (or string) export
like VITE_SENTRY_SMOKE_TEST (or SENTRY_SMOKE_TEST) in utils/config.ts that reads
import.meta.env.VITE_SENTRY_SMOKE_TEST and then import that symbol into
analytics.ts and use it in the if check instead of
import.meta.env.VITE_SENTRY_SMOKE_TEST; update any type/coercion so the
conditional logic in analytics.ts (the existing if) behaves the same.
---
Outside diff comments:
In @.github/workflows/release.yml:
- Around line 551-557: The workflow currently sets deps_dir to
"app/src-tauri/target/${MATRIX_TARGET}/release/deps", which misses the shell
binary debug files; change the directory passed to
scripts/upload_sentry_symbols.sh to the Tauri release target root (e.g., set
deps_dir to "app/src-tauri/target/${MATRIX_TARGET}/release" or a new variable
like target_dir) so the upload script scans recursively and includes the shell
binary and linked core frames; keep the existence check and the call to bash
scripts/upload_sentry_symbols.sh "$VERSION" "$target_dir" (preserving
MATRIX_TARGET and VERSION usage).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4059b2ce-24fb-474f-9554-a75b937c3c37
📒 Files selected for processing (12)
.github/workflows/release-staging.yml.github/workflows/release.ymlapp/.env.exampleapp/src/App.tsxapp/src/components/ErrorFallbackScreen.tsxapp/src/components/ErrorReportNotification.tsxapp/src/main.tsxapp/src/services/analytics.tsapp/src/services/errorReportQueue.test.tsapp/src/services/errorReportQueue.tsapp/src/utils/desktopDeepLinkListener.tsapp/src/vite-env.d.ts
💤 Files with no reviewable changes (4)
- app/src/main.tsx
- app/src/components/ErrorReportNotification.tsx
- app/src/services/errorReportQueue.test.ts
- app/src/services/errorReportQueue.ts
- release.yml: scan release/ root (not release/deps/) for Tauri DIF upload so shell binary debug files are included alongside linked core frames — fixes unsymbolicated desktop Rust crashes (addresses @coderabbitai on .github/workflows/release.yml:551-557) - release-staging.yml: export OPENHUMAN_BUILD_SHA when building the standalone core CLI binary so build_release_tag() produces openhuman@<version>+<sha>, matching the Sentry-uploaded symbol release tag (addresses @coderabbitai on .github/workflows/release-staging.yml:394) - analytics.ts: route VITE_SENTRY_SMOKE_TEST through utils/config.ts (SENTRY_SMOKE_TEST export) instead of reading import.meta.env directly, per the centralized frontend config convention (addresses @coderabbitai on app/src/services/analytics.ts:108) - analytics.ts: bypass the analytics-consent gate in beforeSend for the smoke-test event so VITE_SENTRY_SMOKE_TEST validation works on a fresh install where consent hasn't been granted yet (addresses @coderabbitai on app/src/services/analytics.ts:55)
…dored tauri-build The vendored CEF tauri-build DebConfig uses deny_unknown_fields and does not have an `args` field (added in newer upstream Tauri). PR tinyhumansai#1087 merged this field into main, causing cargo check to fail with "unknown field `args`". Removing it unblocks rust:check; the X11 launcher flags need to be delivered via a custom desktop-template or a vendored tauri-utils update instead.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/services/analytics.ts`:
- Around line 59-101: The beforeSend handler still forwards free-text fields
that can contain PII; update the beforeSend function to sanitize/redact those
fields by removing or replacing event.message, event.logentry, and
exception.values[*].value (and any nested free-text fields) before returning the
event; locate the beforeSend implementation and ensure you also sanitize
event.exception.values array entries (the value property) and any other
top-level string fields that could leak user input so only non-PII identifiers
(tags, user.id) and allowed contexts remain.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e7870e39-fd92-4fa2-8b50-6749755685c5
📒 Files selected for processing (4)
.github/workflows/release-staging.yml.github/workflows/release.ymlapp/src/services/analytics.tsapp/src/utils/config.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- .github/workflows/release.yml
- .github/workflows/release-staging.yml
| beforeSend(event) { | ||
| // Bypass mode: let the event through (used by sendEventToSentry) | ||
| if (_bypassBeforeSend) { | ||
| _bypassBeforeSend = false; | ||
| return event; | ||
| } | ||
|
|
||
| // --- Sanitize the event --- | ||
| // Always allow the smoke-test event through so pipeline validation works | ||
| // even when the user hasn't opted into analytics yet on first boot. | ||
| const isSmokeTest = event.message === 'react-sentry-smoke-test'; | ||
| // Drop events when the user hasn't opted into analytics. | ||
| if (!isSmokeTest && !isAnalyticsEnabled()) return null; | ||
|
|
||
| // Strip any breadcrumbs that somehow snuck in | ||
| // Strip anything that could carry Redux / localStorage / request bodies. | ||
| event.breadcrumbs = []; | ||
|
|
||
| // Strip request data (cookies, headers, body) | ||
| delete event.request; | ||
|
|
||
| // Strip user PII — keep only a stable anonymous ID | ||
| const userId = getCoreStateSnapshot().snapshot.currentUser?._id; | ||
| event.user = userId ? { id: userId } : undefined; | ||
|
|
||
| // Strip any extra/contexts that could contain Redux or localStorage data | ||
| delete event.extra; | ||
| event.contexts = { | ||
| // Keep only OS / browser / device metadata | ||
| os: event.contexts?.os, | ||
| browser: event.contexts?.browser, | ||
| device: event.contexts?.device, | ||
| }; | ||
|
|
||
| // --- Build a sanitized snapshot for the user to inspect --- | ||
| const sanitized: SanitizedSentryEvent = { | ||
| event_id: event.event_id ?? crypto.randomUUID().replace(/-/g, ''), | ||
| timestamp: typeof event.timestamp === 'number' ? event.timestamp : Date.now() / 1000, | ||
| platform: event.platform ?? 'javascript', | ||
| exception: sanitizeException(event.exception), | ||
| contexts: event.contexts as SanitizedSentryEvent['contexts'], | ||
| user: event.user as SanitizedSentryEvent['user'], | ||
| tags: event.tags as Record<string, string> | undefined, | ||
| environment: IS_DEV ? 'development' : 'production', | ||
| }; | ||
| // Tag with surface so events filter cleanly inside `openhuman-react`. | ||
| event.tags = { ...(event.tags ?? {}), surface: 'react' }; | ||
|
|
||
| // Extract human-readable title + message from the exception | ||
| const firstException = event.exception?.values?.[0]; | ||
| const title = firstException?.type ?? 'Error'; | ||
| const message = firstException?.value ?? 'Unknown error'; | ||
| // Strip PII; keep a stable anonymous user id only. | ||
| const userId = getCoreStateSnapshot().snapshot.currentUser?._id; | ||
| event.user = userId ? { id: userId } : undefined; | ||
|
|
||
| // Queue the error for the notification UI | ||
| enqueueError({ | ||
| id: crypto.randomUUID(), | ||
| timestamp: Date.now(), | ||
| source: 'global', | ||
| title, | ||
| message, | ||
| sentryEvent: sanitized, | ||
| }); | ||
| // Strip frame-level local variables and source context — never send | ||
| // raw source snippets or live variable values to the dashboard. | ||
| if (event.exception?.values) { | ||
| for (const v of event.exception.values) { | ||
| if (v.stacktrace?.frames) { | ||
| for (const f of v.stacktrace.frames) { | ||
| delete f.vars; | ||
| delete f.context_line; | ||
| delete f.pre_context; | ||
| delete f.post_context; | ||
| } | ||
| } | ||
| if (v.mechanism) { | ||
| delete v.mechanism.data; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Return null to prevent Sentry from auto-sending | ||
| return null; | ||
| return event; |
There was a problem hiding this comment.
Exception/message text is still a PII escape hatch.
beforeSend strips structural fields, but it still forwards event.message, event.logentry, and exception.values[*].value. Those strings often contain user input, filenames, URLs, or backend fragments, so the current auto-send path does not fully meet the “No PII” guarantee.
Suggested hardening
beforeSend(event) {
// Always allow the smoke-test event through so pipeline validation works
// even when the user hasn't opted into analytics yet on first boot.
const isSmokeTest = event.message === 'react-sentry-smoke-test';
// Drop events when the user hasn't opted into analytics.
if (!isSmokeTest && !isAnalyticsEnabled()) return null;
+
+ if (!isSmokeTest) {
+ event.message = undefined;
+ event.logentry = undefined;
+ }
// Strip anything that could carry Redux / localStorage / request bodies.
event.breadcrumbs = [];
delete event.request;
delete event.extra;
@@
if (event.exception?.values) {
for (const v of event.exception.values) {
+ v.value = undefined;
if (v.stacktrace?.frames) {
for (const f of v.stacktrace.frames) {
delete f.vars;
delete f.context_line;
delete f.pre_context;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| beforeSend(event) { | |
| // Bypass mode: let the event through (used by sendEventToSentry) | |
| if (_bypassBeforeSend) { | |
| _bypassBeforeSend = false; | |
| return event; | |
| } | |
| // --- Sanitize the event --- | |
| // Always allow the smoke-test event through so pipeline validation works | |
| // even when the user hasn't opted into analytics yet on first boot. | |
| const isSmokeTest = event.message === 'react-sentry-smoke-test'; | |
| // Drop events when the user hasn't opted into analytics. | |
| if (!isSmokeTest && !isAnalyticsEnabled()) return null; | |
| // Strip any breadcrumbs that somehow snuck in | |
| // Strip anything that could carry Redux / localStorage / request bodies. | |
| event.breadcrumbs = []; | |
| // Strip request data (cookies, headers, body) | |
| delete event.request; | |
| // Strip user PII — keep only a stable anonymous ID | |
| const userId = getCoreStateSnapshot().snapshot.currentUser?._id; | |
| event.user = userId ? { id: userId } : undefined; | |
| // Strip any extra/contexts that could contain Redux or localStorage data | |
| delete event.extra; | |
| event.contexts = { | |
| // Keep only OS / browser / device metadata | |
| os: event.contexts?.os, | |
| browser: event.contexts?.browser, | |
| device: event.contexts?.device, | |
| }; | |
| // --- Build a sanitized snapshot for the user to inspect --- | |
| const sanitized: SanitizedSentryEvent = { | |
| event_id: event.event_id ?? crypto.randomUUID().replace(/-/g, ''), | |
| timestamp: typeof event.timestamp === 'number' ? event.timestamp : Date.now() / 1000, | |
| platform: event.platform ?? 'javascript', | |
| exception: sanitizeException(event.exception), | |
| contexts: event.contexts as SanitizedSentryEvent['contexts'], | |
| user: event.user as SanitizedSentryEvent['user'], | |
| tags: event.tags as Record<string, string> | undefined, | |
| environment: IS_DEV ? 'development' : 'production', | |
| }; | |
| // Tag with surface so events filter cleanly inside `openhuman-react`. | |
| event.tags = { ...(event.tags ?? {}), surface: 'react' }; | |
| // Extract human-readable title + message from the exception | |
| const firstException = event.exception?.values?.[0]; | |
| const title = firstException?.type ?? 'Error'; | |
| const message = firstException?.value ?? 'Unknown error'; | |
| // Strip PII; keep a stable anonymous user id only. | |
| const userId = getCoreStateSnapshot().snapshot.currentUser?._id; | |
| event.user = userId ? { id: userId } : undefined; | |
| // Queue the error for the notification UI | |
| enqueueError({ | |
| id: crypto.randomUUID(), | |
| timestamp: Date.now(), | |
| source: 'global', | |
| title, | |
| message, | |
| sentryEvent: sanitized, | |
| }); | |
| // Strip frame-level local variables and source context — never send | |
| // raw source snippets or live variable values to the dashboard. | |
| if (event.exception?.values) { | |
| for (const v of event.exception.values) { | |
| if (v.stacktrace?.frames) { | |
| for (const f of v.stacktrace.frames) { | |
| delete f.vars; | |
| delete f.context_line; | |
| delete f.pre_context; | |
| delete f.post_context; | |
| } | |
| } | |
| if (v.mechanism) { | |
| delete v.mechanism.data; | |
| } | |
| } | |
| } | |
| // Return null to prevent Sentry from auto-sending | |
| return null; | |
| return event; | |
| beforeSend(event) { | |
| // Always allow the smoke-test event through so pipeline validation works | |
| // even when the user hasn't opted into analytics yet on first boot. | |
| const isSmokeTest = event.message === 'react-sentry-smoke-test'; | |
| // Drop events when the user hasn't opted into analytics. | |
| if (!isSmokeTest && !isAnalyticsEnabled()) return null; | |
| if (!isSmokeTest) { | |
| event.message = undefined; | |
| event.logentry = undefined; | |
| } | |
| // Strip anything that could carry Redux / localStorage / request bodies. | |
| event.breadcrumbs = []; | |
| delete event.request; | |
| delete event.extra; | |
| event.contexts = { | |
| os: event.contexts?.os, | |
| browser: event.contexts?.browser, | |
| device: event.contexts?.device, | |
| }; | |
| // Tag with surface so events filter cleanly inside `openhuman-react`. | |
| event.tags = { ...(event.tags ?? {}), surface: 'react' }; | |
| // Strip PII; keep a stable anonymous user id only. | |
| const userId = getCoreStateSnapshot().snapshot.currentUser?._id; | |
| event.user = userId ? { id: userId } : undefined; | |
| // Strip frame-level local variables and source context — never send | |
| // raw source snippets or live variable values to the dashboard. | |
| if (event.exception?.values) { | |
| for (const v of event.exception.values) { | |
| v.value = undefined; | |
| if (v.stacktrace?.frames) { | |
| for (const f of v.stacktrace.frames) { | |
| delete f.vars; | |
| delete f.context_line; | |
| delete f.pre_context; | |
| delete f.post_context; | |
| } | |
| } | |
| if (v.mechanism) { | |
| delete v.mechanism.data; | |
| } | |
| } | |
| } | |
| return event; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src/services/analytics.ts` around lines 59 - 101, The beforeSend handler
still forwards free-text fields that can contain PII; update the beforeSend
function to sanitize/redact those fields by removing or replacing event.message,
event.logentry, and exception.values[*].value (and any nested free-text fields)
before returning the event; locate the beforeSend implementation and ensure you
also sanitize event.exception.values array entries (the value property) and any
other top-level string fields that could leak user input so only non-PII
identifiers (tags, user.id) and allowed contexts remain.
* feat(remotion): Ghosty character library with transparent MOV variants (tinyhumansai#1059) Co-authored-by: WOZCODE <contact@withwoz.com> * feat(composio/gmail): sync into memory tree (Slack-parity) (tinyhumansai#1056) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(scheduler-gate): throttle background AI on battery / busy CPU (tinyhumansai#1062) * fix(core,cef): run core in-process and stop orphaning CEF helpers on Cmd+Q (tinyhumansai#1061) * ci: add dedicated staging release workflow (tinyhumansai#1066) * fix(sentry): Rust source context + per-release deploy marker (tinyhumansai#405) (tinyhumansai#1067) * fix(welcome): re-enable OAuth buttons with focus/timeout recovery (tinyhumansai#1049) (tinyhumansai#1069) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(dependencies): update pnpm-lock.yaml and Cargo.lock for package… (tinyhumansai#1082) * fix(onboarding): personalize welcome agent greeting with user identity (tinyhumansai#1078) * fix(chat): make agent message bubbles fit content width (tinyhumansai#1083) * Feat/dmg checks (tinyhumansai#1084) * fix(linux): Add X11 platform flags to .deb package launcher (tinyhumansai#1087) Co-authored-by: unn-Known1 <unn-known1@users.noreply.github.com> * fix(sentry): auto-send React events; collapse core→tauri for desktop (tinyhumansai#1086) Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * fix(cef): run blank reload guard on the CEF UI thread (tinyhumansai#1092) * fix(app): reload webview instead of restart_app in dev mode (tinyhumansai#1068) (tinyhumansai#1071) * fix(linux): deliver X11 ozone flags via custom .desktop template (tinyhumansai#1091) * fix(webview-accounts): retry data-dir purge so CEF handle race doesn't leak cookies (tinyhumansai#1076) (tinyhumansai#1081) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * fix(webview/slack): media perms + deep-link isolation (tinyhumansai#1074) (tinyhumansai#1080) Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * ci(release): split staging vs production workflows; promote staging tags (tinyhumansai#1094) * Update release-staging.yml (tinyhumansai#1097) * chore(staging): v0.53.5 * chore(staging): v0.53.6 * ci(staging): cut staging from main; add act local-debug helper (tinyhumansai#1099) * chore(staging): v0.53.7 * fix(ci): correct sentry-cli download URL and trap scope (tinyhumansai#1100) * chore(staging): v0.53.8 * feat(chat): forward thread_id to backend for KV cache locality (tinyhumansai#1095) * fix(ci): bump pinned sentry-cli to 3.4.1 (2.34.2 was never published) (tinyhumansai#1102) * chore(staging): v0.53.9 * fix(ci): drop bash trap in upload_sentry_symbols.sh; inline cleanup (tinyhumansai#1103) * chore(staging): v0.53.10 * refactor(session): flatten session_raw/, switch md to YYYY_MM_DD (tinyhumansai#1098) * Add full Composio managed-auth toolkit catalog (tinyhumansai#1093) * ci: add diff-aware 80% coverage gate (Vitest + cargo-llvm-cov) (tinyhumansai#1104) * feat(scripts): pnpm work + pnpm debug for agent-driven workflows (tinyhumansai#1105) * ci: pull pnpm into CI image, drop redundant setup steps (tinyhumansai#1107) * docs: add Cursor Cloud specific instructions to AGENTS.md (tinyhumansai#1106) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * chore(staging): v0.53.11 * docs: surface 80% coverage gate and scripts/debug runners (tinyhumansai#1108) * feat(app): show Composio integrations as sorted icon grid on Skills (tinyhumansai#1109) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * feat(composio): client-side trigger enable/disable toggles (tinyhumansai#1110) * feat(skills): channels grid + integrations card polish; tolerant Composio trigger decode (tinyhumansai#1112) * chore(staging): v0.53.12 * feat(home): early-bird banner + assistant→agent terminology (tinyhumansai#1113) * feat(updater): in-app auto-update with auto-download + restart prompt (tinyhumansai#677) (tinyhumansai#1114) * chore(claude): add ship-and-babysit slash command (tinyhumansai#1115) * feat(home): EarlyBirdyBanner + agent terminology + LinkedIn enrichment model pin (tinyhumansai#1118) * fix(chat): single onboarding thread in sidebar after wizard (tinyhumansai#1116) Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Steven Enamakel <senamakel@users.noreply.github.com> * fix: filter out global namespace from citation chips (tinyhumansai#1124) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> * feat(nav): enable Memory tab in BottomTabBar (tinyhumansai#1125) * feat(memory): singleton ingestion + status RPC + UI pill (tinyhumansai#1126) * feat(human): mascot tab with viseme-driven lipsync (staging only) (tinyhumansai#1127) * Fix CEF zombie processes on full app close and restart (tinyhumansai#1128) Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * Update issue templates for GitHub issue types (tinyhumansai#1146) * feat(human): expand mascot expressions and tighten reply-speech state machine (tinyhumansai#1147) * feat(memory): ingestion pipeline + tree-architecture docs + ops/schemas split (tinyhumansai#1142) * feat(threads): surface live subagent work in parent thread (tinyhumansai#1122) (tinyhumansai#1159) * fix(human): keep mascot mouth animating when TTS ships no viseme data (tinyhumansai#1160) * feat(composio): consume backend markdownFormatted for LLM output (tinyhumansai#1165) * fix(subagent): lazy-register toolkit actions filtered out of fuzzy top-K (tinyhumansai#1162) * feat(memory): user-facing long-term memory window preset (tinyhumansai#1137) (tinyhumansai#1161) * fix(tauri-shell): proactively kill stale openhuman RPC on startup (tinyhumansai#1166) * chore(staging): v0.53.13 * fix(composio): per-action tool consumes backend markdownFormatted (tinyhumansai#1167) * fix(threads): persist selectedThreadId across reloads (tinyhumansai#1168) * feat(memory_tree): switch embed model to bge-m3 (1024-dim, 8K context) (tinyhumansai#1174) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): drop redundant [Memory context] recall injection (tinyhumansai#1173) * chore(memory_tree): drop body-read timeouts on Ollama HTTP calls (tinyhumansai#1171) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(transcript): emit thread_id + fix orchestrator missing cost (tinyhumansai#1169) * fix(composio/gmail): phase out html2md, prefer text/plain MIME part (tinyhumansai#1170) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tools): markdown output for internal tool results (tinyhumansai#1172) * feat(security): enforce prompt-injection guard before model and tool execution (tinyhumansai#1175) * fix(cef): popup paint dies after first frame — skip blank-page guard for popups (tinyhumansai#1079) (tinyhumansai#1182) Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> * chore(sentry): rename OPENHUMAN_SENTRY_DSN → OPENHUMAN_CORE_SENTRY_DSN (tinyhumansai#1186) * feat(remotion): add yellow mascot character with all animation variants (tinyhumansai#1193) Co-authored-by: Neel Mistry <neelmistry@Neels-MacBook-Pro.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(composio): hide raw connection ID, derive friendly label (tinyhumansai#1153) (tinyhumansai#1185) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix(windows): align install.ps1 MSI with per-machine scope (tinyhumansai#913) (tinyhumansai#1187) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(tauri): deterministic CEF teardown on full app close (tinyhumansai#1120) (tinyhumansai#1189) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(composio): cap Gmail HTML body before strip (crash mitigation) (tinyhumansai#1191) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(auth): stop stale chat threads after signup (tinyhumansai#1192) Co-authored-by: Cursor <cursoragent@cursor.com> * feat(sentry): staging-only "Trigger Sentry Test" button (tinyhumansai#1072) (tinyhumansai#1183) * chore(staging): v0.53.14 * chore(staging): v0.53.15 * feat(composio): format trigger slugs into human-readable labels (tinyhumansai#1129) (tinyhumansai#1179) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix(ui): hide unsupported permission UI on non-macOS for Screen Intelligence (tinyhumansai#1194) Co-authored-by: Cursor <cursoragent@cursor.com> * chore(tauri-shell): retire embedded Gmail webview-account flow (tinyhumansai#1181) * feat(onboarding): replace welcome-agent bot with react-joyride walkthrough (tinyhumansai#1180) * chore(release): v0.53.16 * fix(threads): preserve selectedThreadId on cold-boot identity hydration (tinyhumansai#1196) * feat(core): version/shutdown/update RPCs + mid-thread integration refresh (tinyhumansai#1195) * fix(mascot): swap to yellow mascot via @remotion/player (tinyhumansai#1200) * feat(memory_tree): cloud-default LLM, queue priority, entity filter, Memory tab UI (tinyhumansai#1198) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Persist turn state + restore conversation history on cold-boot (tinyhumansai#1202) * feat(mascot): floating desktop mascot via native NSPanel + WKWebView (macOS) (tinyhumansai#1203) * fix(memory/tree): emit summary children as Obsidian wikilinks (tinyhumansai#1210) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tools): coding-harness baseline primitives (tinyhumansai#1205) (tinyhumansai#1208) * docs: add Codex PR checklist for remote agents --------- Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Co-authored-by: WOZCODE <contact@withwoz.com> Co-authored-by: sanil-23 <sanil@vezures.xyz> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Co-authored-by: CodeGhost21 <164498022+CodeGhost21@users.noreply.github.com> Co-authored-by: oxoxDev <164490987+oxoxDev@users.noreply.github.com> Co-authored-by: Mega Mind <146339422+M3gA-Mind@users.noreply.github.com> Co-authored-by: Gaurang Patel <ptelgm.yt@gmail.com> Co-authored-by: unn-Known1 <unn-known1@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Steven Enamakel <senamakel@users.noreply.github.com> Co-authored-by: Steven Enamakel's Droid <enamakel.agent@tinyhumans.ai> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> Co-authored-by: YellowSnnowmann <167776381+YellowSnnowmann@users.noreply.github.com> Co-authored-by: Neil <neil@maha.xyz> Co-authored-by: Neel Mistry <neelmistry@Neels-MacBook-Pro.local> Co-authored-by: obchain <167975049+obchain@users.noreply.github.com> Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
…inyhumansai#1086) Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
Summary
openhuman-reactinstead of queueing them behind a user-opt-in notification — pre-feat(sentry): split errors into per-surface projects (react, core, tauri) #1032 the trickle that landed in Sentry came from users occasionally clicking "Send"; post-split the trickle dried up across three projects, so this PR flips React to classic auto-send (still consent-gated + privacy-stripped).errorReportQueue+ErrorReportNotificationopt-in scaffolding (the in-app toast + queue) and rewires the one user-facing call site (OAuth stale-app version) toSentry.captureMessage+console.warn.openhuman-coreSentry surface intoopenhuman-tauribecause since fix(core,cef): run core in-process and stop orphaning CEF helpers on Cmd+Q #1061 the core lives in-process inside the Tauri shell binary — one Rust process → onesentry::init→ one hub. Repoints the Rust DIF upload step accordingly.VITE_SENTRY_SMOKE_TESTenv-gated one-shot trigger mirroring the existingOPENHUMAN_TAURI_SENTRY_TESTso the pipeline can be verified end-to-end.Problem
After #1032 split errors into three projects (
openhuman-react/openhuman-tauri/openhuman-core), all three projects went silent — the user reported zero events captured in the last 2 hours across all three. Diagnosis surfaced three independent issues, ordered by impact:analytics.ts::beforeSendreturnsnullfor every event unless_bypassBeforeSend = true, queueing each event inerrorReportQueuefor user approval viaErrorReportNotification. Pre-feat(sentry): split errors into per-surface projects (react, core, tauri) #1032 a single project occasionally received approved events; post-split the same trickle is now divided across three projects and effectively dries up.openhuman-coreis structurally dead in the desktop build. PR fix(core,cef): run core in-process and stop orphaning CEF helpers on Cmd+Q #1061 made the core a library linked into the Tauri shell (path depopenhuman_coreinapp/src-tauri/Cargo.toml:114).src/main.rs::main()— the only place that callssentry::initforOPENHUMAN_SENTRY_DSN— only runs for the standalone CLI (release-packages.ymlLinux-arm64, Docker). Whenopenhuman_corecode emitstracing::error!or callssentry::capture_*inside the desktop app, the active Sentry hub is the Tauri shell's, so events route toopenhuman-tauri, notopenhuman-core.release.ymlandrelease-staging.ymlwere uploading the Tauri shell binary's debug info (which covers the linked-in core too) tovars.SENTRY_PROJECT_CORE— a project that no events from the desktop binary ever land in.Solution
React: auto-send with privacy-preserving sanitization (
app/src/services/analytics.ts)beforeSendnow returns the (sanitized) event instead ofnull:isAnalyticsEnabled()is false (readscoreState.analyticsEnabledper-event, so toggling consent in Settings takes effect immediately).sendDefaultPii: false(no IP, no cookies); reduceevent.userto a stable anonymous id only.event.contextsto{os, browser, device}(drops anything that could carry Redux / localStorage state).vars/context_line/pre_context/post_context/mechanism.datafrom every stack frame so live local values + raw source snippets never leave the user's machine.surface: "react".Defaults that already enforce privacy stay: no breadcrumb-producing integrations (
defaultIntegrations: false+ a hand-picked allowlist), no replays, no traces.Removed dead scaffolding
errorReportQueue.ts(+ tests),ErrorReportNotification.tsx, the second React root inmain.tsx, and thetagErrorSourceErrorBoundary callback inApp.tsxare all gone. The OAuth stale-app version notification indesktopDeepLinkListener.ts(which was the last in-app caller ofenqueueError) is rewired to callSentry.captureMessage(..., { level: 'warning', tags: ... })+console.warn— preserves the Sentry signal, drops the in-app toast (the user already opens the download URL externally).Workflow: collapse core → tauri for desktop builds
release.yml— renames "Upload core sidecar debug symbols" → "Upload Rust debug symbols (tauri project)" and points it atvars.SENTRY_PROJECT_TAURI. Comments updated to explain the in-process consequence.release-staging.yml— splits the upload into two steps: Tauri shell DIFs (app/src-tauri/target/<triple>/debug) →SENTRY_PROJECT_TAURI; standalone CLI DIFs (target/<triple>/debug, built by the existing "Build sidecar core binary" step) →SENTRY_PROJECT_CORE. The CLI still has its ownsentry::initinsrc/main.rsand routes toopenhuman-corefor any operator running it.release-packages.ymlunchanged — the Linux-arm64 standalone CLI genuinely usesOPENHUMAN_SENTRY_DSNand routes toopenhuman-core.Smoke trigger
VITE_SENTRY_SMOKE_TEST=truein the build env fires onereact-sentry-smoke-testinfo event atinitSentry()time. Mirrors the existingOPENHUMAN_TAURI_SENTRY_TEST=panic|messagetrigger atapp/src-tauri/src/lib.rs:838. Documented inapp/.env.example. Set for one staging build to verify, then remove.Known limitation (deliberately deferred)
The Tauri shell's
sentry::initcaptures panics (viasentry::panic_integration) and explicit captures, but plaintracing::error!from linked-in core code is not auto-routed to Sentry inside the desktop binary —init_for_cli_run(which installs the sentry-tracing layer atsrc/core/logging.rs:198) only runs for theopenhuman run/serveCLI subcommands, not the in-processrun_server_embeddedpath. Closing this gap means installingsentry::integrations::tracing::layer()inside the Tauri shell process before the core boots; flagged for a follow-up rather than expanding this PR.Submission Checklist
errorReportQueue.test.tsremoved alongside the module it tested. The remaininganalytics.tschange is config (no logic worth unit-testing); the smoke trigger is env-gated dispatch verified manually. All 1033 existing unit tests still pass (pnpm test:unit).docs/TEST-COVERAGE-MATRIX.md; this changes how events are dispatched, not user-visible behaviour.Sentry.captureEventalready exists.docs/RELEASE-MANUAL-SMOKE.md.## Related.Impact
openhuman-coretoopenhuman-taurifor the desktop build.vars.OPENHUMAN_CORE_SENTRY_DSNandvars.SENTRY_PROJECT_COREare no longer read byrelease.ymland only referenced byrelease-staging.ymlfor the standalone CLI upload + byrelease-packages.yml. Safe to keep — they still apply to CLI surfaces.isAnalyticsEnabled()) is now enforced insidebeforeSendon every event.Related
sentry::integrations::tracing::layer()in the Tauri shell process sotracing::error!from in-process core code auto-routes toopenhuman-tauri(current behaviour: only panics + explicit captures reach Sentry).openhuman-coreCLI (Docker / Linux-arm64 / Homebrew) keeps its own Sentry project or also collapses intoopenhuman-taurionce the desktop split settles.VITE_SENTRY_SMOKE_TEST=true+OPENHUMAN_TAURI_SENTRY_TEST=message, then remove the smoke env vars.Summary by CodeRabbit
New Features
Bug Fixes
Chores
Tests