Plugin Crash Reports — Quickstart
A crash-reporting system for audio plugins. A single shared CrashReporter agent (installed once, serves every plugin) scans the OS crash logs, matches each crash to a registered plugin, and uploads it to the site, which symbolicates it server-side from debug symbols you upload at release time.
You then read the symbolicated crashes on the website or from Claude via the MCP server.
How it fits together
your plugin crashes in a host
│
▼
the OS writes its normal crash log (.ips / minidump)
│
▼
next time any plugin launches, the shared CrashReporter runs once,
scans the logs, matches them to registered plugins by your JSON,
and uploads the raw crash ──► crashreports.rabiensoftware.com
│
▼
a worker symbolicates it using the symbols you
uploaded from CI (matched by build UUID / debug-id)
│
▼
view it on the site or via the MCP
Key idea: there is no in-process crash handler. Many plugins share one host and can't each own the handler — so we let the OS write its normal crash log and scan it out-of-process on the next launch.
1. Create an account and register your plugin
- Sign up at https://crashreports.rabiensoftware.com/register/.
- Create a team (or get added to one).
- Register a plugin. You provide:
- Name — matched against the module name in Windows crash dumps (e.g.
MyPlugin). - Plugin code — the macOS
CFBundleIdentifier, e.g.com.vendor.myplugin.
- Name — matched against the module name in Windows crash dumps (e.g.
- On the plugin's Keys page you get two keys:
- Crash API key — goes in the registration JSON below. It ships inside installers (world-readable), so treat it as a public account tag, not a secret.
- Symbol API key — used by CI to upload symbols. Keep this secret.
- (Optional) On the Account page, generate a personal API key to browse crashes from Claude via the MCP server.
2. Create the registration JSON
One JSON descriptor per plugin. Your installer drops it into the CrashReporter's
Plugins folder (see step 3). The reporter reads every descriptor and tests each
system crash log against all of them.
{
"name": "MyPlugin",
"pluginID": "com.vendor.myplugin",
"crashUrl": "https://crashreports.rabiensoftware.com/post/",
"apiKey": "<your plugin's crash API key>"
}
| Field | Meaning |
|---|---|
name |
Matched against the crashing module on Windows. |
pluginID |
Matched against the CFBundleIdentifier on macOS. |
crashUrl |
Upload endpoint — https://crashreports.rabiensoftware.com/post/. |
apiKey |
Your plugin's crash API key. |
3. Bundle the CrashReporter into your installer
The CrashReporter is one shared background app that serves all plugins. Install it as a shared component: install only if newer, never uninstall, and drop your registration JSON next to it.
Fetch the latest signed build from the public distribution channel (or build from source):
# platform = mac | win
curl -fsSL "https://crashreports.rabiensoftware.com/reporter/latest/?platform=mac" -o CrashReporter_Mac.zip
Install the shared app here (upgrade only if newer, never uninstall):
| App / exe | |
|---|---|
| macOS | /Library/Application Support/Rabien Software/Crash Reporter/CrashReporter.app |
| Windows | C:\Program Files\Rabien Software\Crash Reporter\CrashReporter.exe |
The reporter looks for registration JSONs in a Plugins/ folder under two
roots — a system root (written by installers, elevated) and a per-user root
(written by a plugin at runtime). Drop myplugin.json in whichever fits:
| System root | Per-user root | |
|---|---|---|
| macOS | /Library/Application Support/Rabien Software/Crash Reporter/Plugins/ |
~/Library/Application Support/Rabien Software/Crash Reporter/Plugins/ |
| Windows | C:\ProgramData\Rabien Software\Crash Reporter\Plugins\ |
%APPDATA%\Rabien Software\Crash Reporter\Plugins\ |
Windows (Inno Setup)
[Components]
Name: "crashreporter"; Description: "Crash reporter (shared component, only updated if newer)"; Types: full custom; Flags: checkablealone
[Files]
; App: shared, only updated if newer, never removed on uninstall.
Source: "bin\CrashReporter\CrashReporter.exe"; DestDir: "{commonpf}\Rabien Software\Crash Reporter"; Flags: skipifsourcedoesntexist uninsneveruninstall; Components: crashreporter
; Registration JSON: always installed, never removed.
Source: "bin\CrashReporter\myplugin.json"; DestDir: "{commonappdata}\Rabien Software\Crash Reporter\Plugins"; Flags: ignoreversion uninsneveruninstall
macOS (pkgbuild)
Two gotchas make the mac component fiddly:
-
Disable bundle relocation.
pkgbuildmarks the.apprelocatable by default, so the installer redirects it to any existingCrashReporter.appit finds via Spotlight (e.g. a dev build) instead of your staged path. Force it:pkgbuild --analyze --root "$REP_STAGE" reporter-component.plist /usr/libexec/PlistBuddy -c "Set :0:BundleIsRelocatable false" reporter-component.plist -
Only install if newer. Stage the app under a
.incomingfolder and let apostinstallscript promote it to the live location only when its version is strictly greater (shared component — never downgrade it).
REP_ROOT="$REP_STAGE/Library/Application Support/Rabien Software/Crash Reporter"
mkdir -p "$REP_ROOT/Plugins" "$REP_ROOT/.incoming"
unzip -qo CrashReporter_Mac.zip -d "$REP_STAGE"
mv "$REP_STAGE/CrashReporter.app" "$REP_ROOT/.incoming/"
cp myplugin.json "$REP_ROOT/Plugins/"
codesign -s "$DEV_APP_ID" --options=runtime --timestamp --force "$REP_ROOT/.incoming/CrashReporter.app"
pkgbuild --root "$REP_STAGE" --install-location "/" \
--identifier "com.vendor.myplugin.crashreporter.pkg" --version "$VERSION" \
--component-plist reporter-component.plist \
--scripts reporter-scripts \
reporter.pkg
Sign the app with your Developer ID; it gets notarized when you notarize the combined installer.
Windows CI tip: native (non-MSYS)
curlcan't read Git-Bash paths like/d/a/.... Convert withcygpath -m "$path"before passing paths tocurl.
4. Launch the reporter from your plugin
The reporter is a scan-once-then-exit agent. Launch it once per process when your first instance is created, so it picks up any crash from the previous session. It's invisible (LSUIElement on macOS, GUI subsystem on Windows — no window, no console flash).
// If the shared CrashReporter is installed, launch it once per process.
static void launchCrashReporterOnce()
{
static std::once_flag flag;
std::call_once (flag, []
{
#if JUCE_MAC
juce::File app ("/Library/Application Support/Rabien Software/Crash Reporter/CrashReporter.app");
#elif JUCE_WINDOWS
auto app = juce::File::getSpecialLocation (juce::File::globalApplicationsDirectory)
.getChildFile ("Rabien Software").getChildFile ("Crash Reporter").getChildFile ("CrashReporter.exe");
#else
juce::File app;
#endif
if (app.exists())
juce::Process::openDocument (app.getFullPathName(), {});
});
}
Call launchCrashReporterOnce() from your processor constructor (or when the
first editor opens). If the reporter isn't installed, it's a no-op.
5. Build with debug symbols (Release)
Symbolication needs symbols. Emit them in Release builds.
macOS (dSYM) — Xcode/CMake:
set_target_properties(${tgt} PROPERTIES
XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT[variant=Release] "dwarf-with-dsym")
Windows (PDB) — MSVC/CMake:
target_compile_options(${tgt} PRIVATE "$<$<CONFIG:Release>:/Zi>") # full debug info
target_link_options(${tgt} PRIVATE "$<$<CONFIG:Release>:/DEBUG>") # produce the .pdb
target_link_options(${tgt} PRIVATE "$<$<CONFIG:Release>:/OPT:REF>") # restore size opts
target_link_options(${tgt} PRIVATE "$<$<CONFIG:Release>:/OPT:ICF>") # that /DEBUG disables
6. Strip the binaries you ship
This step is essential. Strip symbols from the binaries in your installer so the OS can't symbolicate crash logs locally — otherwise crash logs arrive already symbolicated and the whole server pipeline is pointless. Stripping preserves the build UUID / debug-id, so your uploaded symbols still match.
macOS — strip each format's Mach-O, before codesigning:
strip -x MyPlugin.vst3/Contents/MacOS/MyPlugin
strip -x MyPlugin.component/Contents/MacOS/MyPlugin
strip -x MyPlugin.clap/Contents/MacOS/MyPlugin
Windows — the PDB is a separate file, so there's nothing to strip: just
don't ship the .pdb in your installer.
7. Upload symbols from CI
On each release build, zip the symbols and upload them keyed to the plugin
version. Uploads are write-once per (plugin, platform, version, filename).
macOS — zip the dSYMs (generate any the build didn't emit with dsymutil):
zip -r Symbols_Mac.zip \
AU/MyPlugin.component.dSYM VST3/MyPlugin.vst3.dSYM CLAP/MyPlugin.clap.dSYM
curl -fsS -H "X-API-Key: $SYMBOL_API_KEY" \
-F "platform=mac" -F "version=$VERSION" -F "files[]=@Symbols_Mac.zip" \
https://crashreports.rabiensoftware.com/symbols/
Windows — zip the PDBs and upload with platform=win.
Notes:
- Store
SYMBOL_API_KEYas a CI secret, not in the repo. versionmust match the version reported in the crash (CFBundleShortVersionStringon macOS, file version on Windows).- Symbolication is asynchronous: if no matching symbols exist yet, the crash waits and is retried automatically once you upload them — so a crash that arrives before its symbols still resolves later.
Viewing crashes
- Website: https://crashreports.rabiensoftware.com → your plugin → Recent / Frequent / a crash's Log.
- Claude (MCP): install
crashwebsite-mcp,
set
CRASH_API_KEYto your personal API key, and ask things like "what are the top crashes for MyPlugin this week?" or "open crash 42 and explain the stack."
Checklist
- Account created; plugin registered; crash + symbol keys copied.
-
myplugin.jsoncreated with the crash API key. - Installer bundles the CrashReporter (shared, if-newer, never-uninstall) + the JSON.
- Plugin calls
launchCrashReporterOnce()on first instance. - Release build emits dSYM (macOS) / PDB (Windows).
- Shipped binaries are stripped (macOS) or ship without
.pdb(Windows). - CI uploads symbols on release, versioned to match the build.