diff --git a/.github/workflows/release-staging.yml b/.github/workflows/release-staging.yml index e7907b4bc..a6f2849ea 100644 --- a/.github/workflows/release-staging.yml +++ b/.github/workflows/release-staging.yml @@ -22,9 +22,21 @@ jobs: prepare-build: name: Prepare build context runs-on: ubuntu-latest + # Reuse the Production GitHub Actions environment so Sentry vars + # (`OPENHUMAN_*_SENTRY_DSN`, `SENTRY_PROJECT_*`, `SENTRY_ORG`, + # `SENTRY_AUTH_TOKEN`) and `VITE_DEBUG` resolve here too. Staging + # events differentiate from production via the `environment` tag set + # at runtime — separate Sentry projects are not needed. + environment: Production outputs: version: ${{ steps.resolve.outputs.version }} sha: ${{ steps.resolve.outputs.sha }} + # First 12 chars of `sha`. Matches the truncation runtime code in + # config.ts / vite.config.ts / main.rs / app/src-tauri/src/lib.rs + # applies when computing `openhuman@+`. Use this + # (not `sha`) anywhere CI constructs SENTRY_RELEASE so uploaded + # artifacts attach to the same release events report. + short_sha: ${{ steps.resolve.outputs.short_sha }} build_ref: ${{ steps.resolve.outputs.build_ref }} base_url: ${{ steps.resolve.outputs.base_url }} steps: @@ -48,8 +60,14 @@ jobs: run: | VERSION="$(node -p "require('./app/package.json').version")" SHA="$(git rev-parse HEAD)" + # Match the 12-char truncation runtime code applies to + # VITE_BUILD_SHA / OPENHUMAN_BUILD_SHA when constructing the + # release tag at startup, so SENTRY_RELEASE assembled in CI + # agrees with the tag events emit. + SHORT_SHA="${SHA:0:12}" echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "sha=$SHA" >> "$GITHUB_OUTPUT" + echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT" echo "build_ref=$SHA" >> "$GITHUB_OUTPUT" echo "base_url=https://staging-api.tinyhumans.ai/" >> "$GITHUB_OUTPUT" @@ -60,6 +78,13 @@ jobs: name: "Desktop: ${{ matrix.settings.artifact_suffix }}" needs: [prepare-build] runs-on: ${{ matrix.settings.platform }} + # Reuse the Production GH Actions environment so the Sentry vars and + # `SENTRY_AUTH_TOKEN` secret resolve here. Staging builds tag their + # events with `environment: staging` at runtime (set by APP_ENVIRONMENT + # in app/src/utils/config.ts and resolve_sentry_environment() in the + # Rust shell), so they're filterable in Sentry without separate + # projects. + environment: Production strategy: fail-fast: false matrix: @@ -271,12 +296,87 @@ jobs: BASE_URL: ${{ needs.prepare-build.outputs.base_url }} VITE_BACKEND_URL: ${{ needs.prepare-build.outputs.base_url }} VITE_DEBUG: ${{ vars.VITE_DEBUG }} + # ----- Sentry wiring (mirrors release.yml's build-desktop step) ----- + # React frontend Sentry DSN — baked by the Vite plugin via + # `import.meta.env.VITE_SENTRY_DSN`. Without this, `Sentry.init` + # in app/src/services/analytics.ts returns early and no events + # report from the staging build. + VITE_SENTRY_DSN: ${{ vars.OPENHUMAN_REACT_SENTRY_DSN }} + # Tauri shell DSN — baked into the shell binary via + # `option_env!("OPENHUMAN_TAURI_SENTRY_DSN")` at compile time. + OPENHUMAN_TAURI_SENTRY_DSN: ${{ vars.OPENHUMAN_TAURI_SENTRY_DSN }} + # Build SHA threaded through to both the Vite bundle and the + # Tauri shell so all surfaces emit the same release tag. + OPENHUMAN_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }} + VITE_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }} + # Use short_sha (12 chars) — matches what config.ts / + # vite.config.ts / main.rs / app/src-tauri/src/lib.rs all slice + # VITE_BUILD_SHA / OPENHUMAN_BUILD_SHA down to at runtime when + # emitting events. The vite plugin reads SENTRY_RELEASE raw, so + # a long-SHA value here would tag uploads against a different + # release than events report. + SENTRY_RELEASE: + openhuman@${{ needs.prepare-build.outputs.version }}+${{ + needs.prepare-build.outputs.short_sha }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ vars.SENTRY_ORG }} + # SENTRY_PROJECT here is consumed by sentry-vite-plugin during + # the Vite build — uploads frontend source maps to the React + # Sentry project. The Rust DIFs go to SENTRY_PROJECT_CORE in the + # dedicated step below. + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_REACT }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }} TAURI_CONFIG_OVERRIDE: ${{ steps.config-overrides.outputs.json }} MATRIX_ARGS: ${{ matrix.settings.args }} run: | NODE_OPTIONS="--max-old-space-size=8192" cargo tauri build --debug -c "$TAURI_CONFIG_OVERRIDE" $MATRIX_ARGS + # Upload Rust debug info to Sentry so backend + Tauri-shell stack + # traces symbolicate in the staging Sentry project too. Frontend + # source maps are handled by sentry-vite-plugin in the build step + # above; this is the Rust half. Symbols are keyed by debug-ID, so + # it's safe to run per-matrix-target without collisions — Sentry + # merges artifacts across platforms. + - name: Upload core sidecar debug symbols to Sentry + if: env.SENTRY_AUTH_TOKEN != '' + shell: bash + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ vars.SENTRY_ORG }} + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_CORE }} + # Must match the release tag the running core binary reports + # (`openhuman@+`, see `build_release_tag` in + # src/main.rs). Uses short_sha (12 chars) — see the prepare-build + # outputs comment. If this drifts, DIFs attach to a different + # release than events and stack traces stay un-symbolicated. + SENTRY_RELEASE: + openhuman@${{ needs.prepare-build.outputs.version }}+${{ + needs.prepare-build.outputs.short_sha }} + VERSION: ${{ needs.prepare-build.outputs.version }} + MATRIX_TARGET: ${{ matrix.settings.target }} + run: | + set -euo pipefail + # Two DIF locations on staging: + # - `app/src-tauri/target//debug` — Tauri shell binary + # (which links the core as a path dep, so its DWARF covers core + # too). + # - `target//debug` — standalone `openhuman-core` CLI + # binary built by the "Build sidecar core binary" step earlier + # and published as a separate artifact. Crashes from operators + # running the standalone CLI need its DIFs to symbolicate. + # Symbols are keyed by debug-ID so cross-platform / cross-binary + # uploads don't collide — Sentry just merges them. + for dif_dir in \ + "app/src-tauri/target/${MATRIX_TARGET}/debug" \ + "target/${MATRIX_TARGET}/debug"; do + 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 $dif_dir (not present)" + fi + done + - name: Upload staging desktop bundles uses: actions/upload-artifact@v4 with: @@ -294,3 +394,44 @@ jobs: }} path: | ${{ steps.core-paths.outputs.core_target_dir }}/${{ steps.core-paths.outputs.core_bin_name }}${{ matrix.settings.platform == 'windows-latest' && '.exe' || '' }} + + # ========================================================================= + # Phase 3: Record a single Sentry deploy marker once the matrix is + # complete. Lives in its own job (not inside `build-desktop`) because + # `sentry-cli releases deploys ... new` does NOT deduplicate by + # (release, env) — running it inside the matrix would add one row per + # platform (×4 on this workflow). One row per release is the right + # shape: re-runs of CI for the same release intentionally produce + # additional rows representing separate deploy attempts. + # ========================================================================= + record-sentry-deploy: + name: Record Sentry deploy marker + runs-on: ubuntu-latest + environment: Production + needs: [prepare-build, build-desktop] + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + steps: + - name: Install sentry-cli + if: env.SENTRY_AUTH_TOKEN != '' + shell: bash + run: curl -sSf https://sentry.io/get-cli/ | bash + - name: Record deploy marker + if: env.SENTRY_AUTH_TOKEN != '' + shell: bash + env: + SENTRY_ORG: ${{ vars.SENTRY_ORG }} + # Marker lives on the React project's release; events from all + # surfaces share the same `openhuman@+` + # release tag, so the marker on any single project's release + # shows in Sentry's "Deploys" tab for that release group. + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_REACT }} + SENTRY_RELEASE: + openhuman@${{ needs.prepare-build.outputs.version }}+${{ + needs.prepare-build.outputs.short_sha }} + SENTRY_ENVIRONMENT: staging + run: | + set -euo pipefail + echo "==> Recording deploy marker: ${SENTRY_RELEASE} -> ${SENTRY_ENVIRONMENT}" + sentry-cli releases deploys "${SENTRY_RELEASE}" new \ + -e "${SENTRY_ENVIRONMENT}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6799929d..7aa54d449 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,6 +54,13 @@ jobs: version: ${{ steps.resolve.outputs.version }} tag: ${{ steps.resolve.outputs.tag }} sha: ${{ steps.resolve.outputs.sha }} + # First 12 chars of `sha` — matches the truncation done at runtime by + # app/src/utils/config.ts, app/vite.config.ts, src/main.rs, and + # app/src-tauri/src/lib.rs when they compute the canonical + # `openhuman@+` release tag. Use this (not the + # full `sha`) anywhere CI constructs SENTRY_RELEASE so uploaded + # artifacts attach to the same release events report. + short_sha: ${{ steps.resolve.outputs.short_sha }} build_ref: ${{ steps.resolve.outputs.build_ref }} release_enabled: ${{ steps.resolve.outputs.release_enabled }} base_url: ${{ steps.resolve.outputs.base_url }} @@ -150,9 +157,15 @@ jobs: RELEASE_ENABLED="false" BASE_URL="https://staging-api.tinyhumans.ai/" fi + # Match the 12-char truncation runtime code applies to + # VITE_BUILD_SHA / OPENHUMAN_BUILD_SHA when constructing the release + # tag at startup, so SENTRY_RELEASE assembled in CI agrees with + # the tag events emit. + SHORT_SHA="${SHA:0:12}" echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "tag=$TAG" >> "$GITHUB_OUTPUT" echo "sha=$SHA" >> "$GITHUB_OUTPUT" + echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT" echo "build_ref=$BUILD_REF" >> "$GITHUB_OUTPUT" echo "release_enabled=$RELEASE_ENABLED" >> "$GITHUB_OUTPUT" echo "base_url=$BASE_URL" >> "$GITHUB_OUTPUT" @@ -467,9 +480,14 @@ jobs: # by Tauri so source maps uploaded there resolve correctly against the # final bundle produced by the tauri-driven build. VITE_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }} + # Use short_sha (12 chars) — matches what config.ts / vite.config.ts / + # main.rs / app/src-tauri/src/lib.rs all slice VITE_BUILD_SHA / + # OPENHUMAN_BUILD_SHA down to at runtime when emitting events. The + # vite plugin reads SENTRY_RELEASE raw, so a long-SHA value here + # would tag uploads against a different release than events report. SENTRY_RELEASE: openhuman@${{ needs.prepare-build.outputs.version }}+${{ - needs.prepare-build.outputs.sha }} + needs.prepare-build.outputs.short_sha }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ vars.SENTRY_ORG }} # SENTRY_PROJECT here is consumed by sentry-vite-plugin during the @@ -513,12 +531,13 @@ jobs: SENTRY_ORG: ${{ vars.SENTRY_ORG }} SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_CORE }} # Must match the release tag the running core binary reports - # (`openhuman@+`, see `build_release_tag` in - # src/main.rs). If this drifts, DIFs attach to a different + # (`openhuman@+`, see `build_release_tag` in + # src/main.rs). Uses short_sha (12 chars) — see the prepare-build + # outputs comment. If this drifts, DIFs attach to a different # release than events and stack traces stay un-symbolicated. SENTRY_RELEASE: openhuman@${{ needs.prepare-build.outputs.version }}+${{ - needs.prepare-build.outputs.sha }} + needs.prepare-build.outputs.short_sha }} VERSION: ${{ needs.prepare-build.outputs.version }} MATRIX_TARGET: ${{ matrix.settings.target }} run: | @@ -666,6 +685,52 @@ jobs: app/src-tauri/target/${{ matrix.settings.target }}/${{ inputs.build_target == 'staging' && 'debug' || 'release' }}/bundle/** target/${{ matrix.settings.target }}/${{ inputs.build_target == 'staging' && 'debug' || 'release' }}/bundle/** + # ========================================================================= + # Phase 5: Record a single Sentry deploy marker once the release has + # actually been published. Hangs off `publish-release` rather than + # `build-desktop` so we don't write a Sentry deploy row for a release + # that subsequently fails `build-docker` / `publish-updater-manifest` + # and gets deleted by `cleanup-failed-release`. `publish-release` itself + # already requires the full build matrix + docker + updater manifest to + # succeed, so this transitively waits for all of them. + # + # `sentry-cli releases deploys ... new` does NOT deduplicate by + # (release, env), so this job stays single-runner — running it inside + # the matrix would add one row per platform. + # ========================================================================= + record-sentry-deploy: + name: Record Sentry deploy marker + runs-on: ubuntu-latest + environment: Production + needs: [prepare-build, publish-release] + if: needs.prepare-build.outputs.release_enabled == 'true' + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + steps: + - name: Install sentry-cli + if: env.SENTRY_AUTH_TOKEN != '' + shell: bash + run: curl -sSf https://sentry.io/get-cli/ | bash + - name: Record deploy marker + if: env.SENTRY_AUTH_TOKEN != '' + shell: bash + env: + SENTRY_ORG: ${{ vars.SENTRY_ORG }} + # Marker lives on the React project's release; events from all + # surfaces share the same `openhuman@+` + # release tag, so the marker on any single project's release + # shows in Sentry's "Deploys" tab for that release group. + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_REACT }} + SENTRY_RELEASE: + openhuman@${{ needs.prepare-build.outputs.version }}+${{ + needs.prepare-build.outputs.short_sha }} + SENTRY_ENVIRONMENT: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }} + run: | + set -euo pipefail + echo "==> Recording deploy marker: ${SENTRY_RELEASE} -> ${SENTRY_ENVIRONMENT}" + sentry-cli releases deploys "${SENTRY_RELEASE}" new \ + -e "${SENTRY_ENVIRONMENT}" + # ========================================================================= # Phase 3b: Build & push Docker image (runs parallel with build-desktop) # ========================================================================= diff --git a/Cargo.lock b/Cargo.lock index 41c0a11e3..f9ce2f11c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2055,6 +2055,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "fixedbitset" version = "0.5.7" @@ -6239,6 +6251,7 @@ dependencies = [ "sentry-backtrace", "sentry-contexts", "sentry-core", + "sentry-debug-images", "sentry-panic", "sentry-tracing", "tokio", @@ -6283,6 +6296,16 @@ dependencies = [ "url", ] +[[package]] +name = "sentry-debug-images" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd9646a972b57896d4a92ed200cf76139f8e30b3cfd03b6662ae59926d26633c" +dependencies = [ + "findshlibs", + "sentry-core", +] + [[package]] name = "sentry-panic" version = "0.47.0" @@ -8259,7 +8282,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 761e5d447..789e46887 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ tower = { version = "0.5", default-features = false } opentelemetry = { version = "0.31", default-features = false, features = ["trace", "metrics"] } opentelemetry_sdk = { version = "0.31", default-features = false, features = ["trace", "metrics"] } opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] } -sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "tracing", "reqwest", "rustls"] } +sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "tracing", "debug-images", "reqwest", "rustls"] } tokio-stream = { version = "0.1.18", features = ["full"] } url = "2" socketioxide = { version = "0.15", features = ["extensions"] } @@ -164,6 +164,17 @@ whatsapp-web = ["dep:whatsapp-rust", "dep:whatsapp-rust-tokio-transport", "dep:w [patch.crates-io] whisper-rs-sys = { git = "https://github.com/tinyhumansai/whisper-rs-sys.git", branch = "main" } +# Emit just enough DWARF in release builds for Sentry to symbolicate Rust +# panics + render surrounding source lines. `line-tables-only` keeps the +# binary small (only file+line tables, no full type info) while still +# letting `sentry-cli debug-files upload --include-sources` produce a +# usable `.src.zip`. `split-debuginfo = "packed"` writes the debug data +# into a separate `.dSYM` bundle on macOS so the shipped executable +# itself stays slim. +[profile.release] +debug = "line-tables-only" +split-debuginfo = "packed" + # Fast CI builds: trade runtime perf for compile speed [profile.ci] inherits = "release" diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index bd66fb6ca..96b90bfc8 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -218,7 +218,7 @@ dependencies = [ "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", - "windows-sys 0.60.2", + "windows-sys 0.59.0", "x11rb", ] @@ -1292,7 +1292,7 @@ dependencies = [ "jni 0.21.1", "js-sys", "libc", - "mach2", + "mach2 0.4.3", "ndk 0.8.0", "ndk-context", "oboe", @@ -1676,7 +1676,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1987,7 +1987,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2089,6 +2089,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "flate2" version = "1.1.9" @@ -3429,6 +3441,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -3633,6 +3651,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + [[package]] name = "mail-parser" version = "0.11.2" @@ -4013,7 +4040,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4557,6 +4584,7 @@ dependencies = [ "sha2 0.10.9", "shellexpand", "socketioxide", + "starship-battery", "sysinfo", "tar", "tempfile", @@ -5459,7 +5487,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -5885,7 +5913,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5944,7 +5972,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6129,6 +6157,7 @@ dependencies = [ "sentry-backtrace", "sentry-contexts", "sentry-core", + "sentry-debug-images", "sentry-panic", "sentry-tracing", "tokio", @@ -6173,6 +6202,16 @@ dependencies = [ "url", ] +[[package]] +name = "sentry-debug-images" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd9646a972b57896d4a92ed200cf76139f8e30b3cfd03b6662ae59926d26633c" +dependencies = [ + "findshlibs", + "sentry-core", +] + [[package]] name = "sentry-panic" version = "0.47.0" @@ -6651,6 +6690,24 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "starship-battery" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0efc2c44c92705be724265a0c758e3b7c120ea63817d2d684bab86fbeced9a" +dependencies = [ + "cfg-if", + "core-foundation 0.10.1", + "lazycell", + "libc", + "mach2 0.5.0", + "nix 0.30.1", + "num-traits", + "plist", + "uom", + "windows-sys 0.61.2", +] + [[package]] name = "stop-token" version = "0.7.0" @@ -7246,7 +7303,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7998,6 +8055,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "uom" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd5cfe7d84f6774726717f358a37f5bca8fca273bed4de40604ad129d1107b49" +dependencies = [ + "num-traits", + "typenum", +] + [[package]] name = "ureq" version = "3.3.0" @@ -8535,7 +8602,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index 463c84de9..9ad1bc961 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -89,7 +89,7 @@ env_logger = "0.11" # can be overridden at runtime via the same env var. Feature set mirrors the # core sidecar (`Cargo.toml` at repo root) minus `tracing` since the shell # uses `log` + `env_logger`. -sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "reqwest", "rustls"] } +sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "debug-images", "reqwest", "rustls"] } # Used by the imessage_scanner module. anyhow = "1.0" @@ -169,6 +169,17 @@ tauri-plugin-notification = { path = "vendor/tauri-plugin-notification" } tokio = { version = "1", features = ["macros", "rt"] } tempfile = "3" +# Emit just enough DWARF in release builds for Sentry to symbolicate Rust +# panics + render surrounding source lines. `line-tables-only` keeps the +# binary small (only file+line tables, no full type info) while still +# letting `sentry-cli debug-files upload --include-sources` produce a +# usable `.src.zip`. `split-debuginfo = "packed"` writes the debug data +# into a separate `.dSYM` bundle on macOS so the shipped executable +# itself stays slim. +[profile.release] +debug = "line-tables-only" +split-debuginfo = "packed" + # Fast CI builds: trade runtime perf for compile speed [profile.ci] inherits = "release" diff --git a/docs/sentry.md b/docs/sentry.md index 98f00b41b..1f6141d60 100644 --- a/docs/sentry.md +++ b/docs/sentry.md @@ -2,13 +2,17 @@ _Tracks issue [#405](https://github.com/tinyhumansai/openhuman/issues/405)._ -OpenHuman reports crashes and errors from two surfaces that must group under -a **single Sentry release** so a new deploy's regressions are easy to see: +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`. -- **Rust core sidecar** — `sentry::init` in `src/main.rs`. - -The Tauri shell binary (`app/src-tauri`) has no Sentry wiring today. +- **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 @@ -61,6 +65,52 @@ 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//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 @@ -69,21 +119,31 @@ build steps. Any subset can be set on a per-environment basis in the ### Required for upload to work -| Name | Type | Scope | Purpose | -| ------------------------------------- | -------- | --------------- | --------------------------------------------- | -| `secrets.SENTRY_AUTH_TOKEN` | secret | build-desktop | Auth for `@sentry/vite-plugin` uploads | -| `vars.SENTRY_ORG` | variable | build-desktop | Sentry org slug | -| `vars.SENTRY_PROJECT_FRONTEND` | variable | build-desktop | Sentry project slug for the frontend bundle | -| `vars.OPENHUMAN_SENTRY_DSN` | variable | build-desktop | Core sidecar DSN (baked via `option_env!`) | -| `vars.VITE_SENTRY_DSN` | variable | build-desktop | Frontend DSN (baked by Vite define) | +| 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) | -| `OPENHUMAN_BUILD_SHA` | Same — passed to `cargo build` for the sidecar | -| `SENTRY_RELEASE` | `openhuman@+` — same on both steps | +| 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@+` — `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) @@ -140,3 +200,21 @@ For the frontend, put `VITE_SENTRY_DSN` in `app/.env.local`. - **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@+` + 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`). diff --git a/scripts/ci-secrets.example.json b/scripts/ci-secrets.example.json index 41ff463a1..7683fa1b2 100644 --- a/scripts/ci-secrets.example.json +++ b/scripts/ci-secrets.example.json @@ -11,14 +11,20 @@ "TAURI_SIGNING_PRIVATE_KEY": "", "XGH_TOKEN": "", "XGITHUB_APP_ID": "", - "XGITHUB_APP_PRIVATE_KEY": "" + "XGITHUB_APP_PRIVATE_KEY": "", + "SENTRY_AUTH_TOKEN": "" }, "vars": { "BASE_URL": "https://localhost", "VITE_BACKEND_URL": "https://localhost:5005", "VITE_SKILLS_GITHUB_REPO": "", - "OPENHUMAN_SENTRY_DSN": "", - "VITE_SENTRY_DSN": "", - "VITE_DEBUG": "true" + "VITE_DEBUG": "true", + "SENTRY_ORG": "", + "SENTRY_PROJECT_REACT": "", + "SENTRY_PROJECT_CORE": "", + "SENTRY_PROJECT_TAURI": "", + "OPENHUMAN_REACT_SENTRY_DSN": "", + "OPENHUMAN_CORE_SENTRY_DSN": "", + "OPENHUMAN_TAURI_SENTRY_DSN": "" } } diff --git a/scripts/upload_sentry_symbols.sh b/scripts/upload_sentry_symbols.sh index a81b0f716..9b344ae07 100644 --- a/scripts/upload_sentry_symbols.sh +++ b/scripts/upload_sentry_symbols.sh @@ -199,7 +199,10 @@ upload_symbols() { "--org" "${SENTRY_ORG}" "--project" "${SENTRY_PROJECT}" "--include-sources" - "--log-level=warning" + # sentry-cli 3.x renamed `warning` → `warn`. Use the short form; + # `warning` is rejected as `invalid value '...' for '--log-level'` + # on 3.x and the script silently skips uploads. + "--log-level=warn" ) # Find and upload all debug symbol files