Hands
Hands Docs

Electron SDK

Crashpad minidump crash reporting for Electron apps (main + renderer) via @botiverse/hands-electron.

@botiverse/hands-electron adds crash reporting to Electron apps. Electron's built-in Crashpad captures native minidumps for both the main and renderer processes and uploads them directly to Hands, where they become crash tickets and are symbolicated server-side against your uploaded Breakpad symbols.

Like @sentry/electron, the SDK has two entry points — one imported in the main process, one in the renderer.

Install

npm install @botiverse/hands-electron

electron is a peer dependency provided by your app.

Main process

Call init() once, before the app is ready:

import { app } from "electron";
import * as Hands from "@botiverse/hands-electron/main";

Hands.init({
  appSlug: "my-desktop-app",   // your Hands app slug
  clientKey: "qk_live_...",    // public client key (safe to ship)
  release: app.getVersion(),   // version_name; defaults to app.getVersion()
  versionCode: 1020300,        // Hands version_code → selects the symbol set
  environment: "stable",       // channel
  extra: { deployment: "ga" }, // static annotations on every crash
  onCrash: (info) => {
    // renderer / child-process termination, including oom / killed exits that
    // produce no minidump — log it, show a recovery dialog, etc.
    console.warn("process gone:", info.processType, info.reason);
  },
});

Attach context that rides along on the next crash:

Hands.setUser({ id: "u_123" });
Hands.setTag("feature", "editor");
Hands.setExtra("open_docs", 3);
Hands.addBreadcrumb({ message: "opened project", category: "ui" });

The main entry starts Crashpad, listens for render-process-gone and child-process-gone, and receives scope forwarded from renderers.

Renderer process

Renderer crashes are captured by the main-process Crashpad automatically. The renderer entry only manages scope and forwards it to main over IPC:

import * as Hands from "@botiverse/hands-electron/renderer";

Hands.setTag("route", location.pathname);
Hands.addBreadcrumb({ message: "clicked export" });

For sandboxed renderers (contextIsolation: true), expose the API from your preload script instead:

// preload.ts
import { exposeHands } from "@botiverse/hands-electron/preload";
exposeHands(); // → window.hands.setTag(...), window.hands.addBreadcrumb(...)

Symbols

Minidumps become readable stacks only when Hands has your app's Breakpad symbols for that version_code. In CI:

  1. Generate .sym files with dump_syms for your app and the Electron framework binaries.
  2. Zip them (a flat zip is fine — Hands reads each file's MODULE header to place it in the Breakpad tree).
  3. Upload alongside the release:
hands builds publish-electron my-desktop-app \
  --version-name 1.2.3 --version-code 1020300 \
  --installer dist/MyApp-1.2.3.exe \
  --symbols symbols.zip

Each crash ticket then gets a symbolicated stack posted as a comment. Without symbols, Hands still records the crash with module+offset frames and leaves a tip to upload them.

What gets sent

Every minidump carries a Sentry-style annotation set the server folds into the ticket: product_type=electron, version, version_code, environment/channel, platform, arch, process_type, electron_version, chrome_version, plus any extra and the current user / tags / breadcrumbs.

How it reaches Hands

The SDK points Electron's crashReporter.submitURL at POST /public/v2/apps/<slug>/minidump?client_key=<key>. Crashpad POSTs the minidump as upload_file_minidump with the annotations as form fields; Hands stores the dump as a crash-ticket attachment and runs the symbolication lane. No update-check or feedback wiring is required for crash reporting.