Repository navigation
app: a crash report says whose code crashed - #351
Merged
Merged
Conversation
fizzy's panic handler, std's segfault handler (now on in every build mode) and a SIGABRT/SIGTRAP handler write one report per crash to stderr and <config>/crashes/: who crashed (fizzy or which plugin), the loaded modules with their build ids, and each frame as module+offset. Plugin dylibs enter a fixed module table before their register runs and leave it before dlclose; ranges and build ids are read from the mapped headers at load. ELF builds now carry a build id. Part of plans/RESTART_AND_CRASHES_PLAN.md, step 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 of 9 tasks
foxnne
added a commit
that referenced
this pull request
Oct 10, 2026
Part of `plans/RESTART_AND_CRASHES_PLAN.md`, step 3 (crash reports). Follows #351. ## What changes **A plugin's panic now reaches fizzy's crash report, message included.** Before, a plugin dylib panicked through its own copy of std. That printed the message to stderr, which the report never saw, and called `abort()`. On macOS and Linux, #351's abort handler then wrote `crashed in <plugin>: Abort` with no message. On Windows, std's abort is `RtlExitUserProcess(3)`, which no handler sees, so no report at all. Now the report reads: ``` fizzy crashed in crashy 0.3.1: panic: crashy was asked to panic … frames: crashy+0x1b6ea3 crashy+0x1b6fbf fizzy+0x1a046f3 … ``` The frames start at the plugin's panic site (`crashy+0x1b6ea3` is `0x12a2eaea3 in register` in std's trace), with no `abort`/`libsystem` frames in front. **How:** - **Plugin side:** the generated dylib root (`sdk/plugin_sdk.zig`, and `plugins/shared/build/helpers.zig` for built-ins) declares `pub const panic = sdk.dylib.panic`. That is `FullPanic` over a forwarder, so every safety panic goes through it too. - **The handoff:** the forwarder hands the message and the first trace address to the host through a new `EditorAPI.panic`. - **Host side:** fizzy raises it again as its own panic (`std.debug.panicExtra`). Natively that is #351's `crash.panic`; on the web it is the web backend's handler, which logs to the console where a side module used to trap silently. - **Fallback:** before the host is installed (`sdk.runtime.installedHost()`, new; the runtime's host pointer is now optional rather than `undefined`), the dylib falls back to std's own handler. ## SDK impact - [ ] None - [ ] Core-only or additive - [x] Fingerprint moved: `EditorAPI` gains `panic`; recorded `0x65096e9408ddb32f` in `sdk/src/version.zig`, `sdk_version` untouched. Plugins get the forwarding at the next SDK release, once they rebuild against it. A plugin built against an older SDK keeps the old behavior, and the fingerprint keeps it from loading into a fizzy built from this commit. - [ ] SDK release ## Verified - [x] **macOS (arm64), sandbox profile, throwaway plugin that panics in `register` on request:** - Debug: the report above, with the message and frames from the panic site. - ReleaseFast fizzy and plugin: `fizzy crashed in crashy 0.3.1: panic: crashy was asked to panic`, first frame `crashy+0x1d173`. - A plugin segfault (ReleaseFast) is unchanged: `crashed in crashy 0.3.1: Segmentation fault at address 0x10`. - [x] **Gates:** `zig build`, `zig build test` (87/87 steps, 531/532, 1 skipped), `test-integration` (46/46 steps, 371/371), `check-web`, `test-sdk-version`. - [x] **Web:** `plugins/image` builds as a wasm side module (`-Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall`) with the new root. Not run in a browser. - [ ] **Windows:** CI cross-compile only. This is the case it newly covers (a plugin's panic reports instead of exiting silently); not run. - [ ] **Linux:** CI. ## Follow-ups - The rest of step 3: the short paste-able report string, a notice at the next launch (which also says when step 4's checkpoint recovered unsaved work, `KeptDocuments.recoveredFromCheckpoint()`), symbols by build id, and safe mode. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
added a commit
that referenced
this pull request
Oct 11, 2026
## What changes `sdk_version` goes from 0.2.22 to 0.2.23. Merging tags `sdk-v0.2.23`, publishes its tarball, and asks the store plugins to repin. **Why now.** The plugin boundary's fingerprint moved since `sdk-v0.2.22` (`0x1bf417f6…` → `0x65096e94…`, by #360), so store plugins built against 0.2.22 don't load in a fizzy built from `main`, and fizzyed.it/app is built from `main`. Also, #355 moved the `plugins` service to version 2, and the agent plugin's `fizzy_build` asks for version 1 until it repins. ## SDK impact - [ ] None - [ ] Core-only or additive: reaches plugins at the next SDK release - [ ] Fingerprint moved: recorded in `sdk/src/version.zig`, `sdk_version` untouched, PR labelled `sdk` - [x] SDK release: bumps `sdk_version`, lists the `sdk` PRs since the last `sdk-v*` tag **The `sdk` PRs since `sdk-v0.2.22`:** - #360: a plugin's panic message reaches its crash report. This moves the fingerprint. **Additive, reaching plugins with this release:** - #355: the `plugins` service is version 2, with `list` (each plugin's identity, link, version, registration counts and status). A caller built for version 1 is refused until it rebuilds, which this release's repin does. - #351: a crash report says whose code crashed (`sdk/plugin_sdk.zig`). **Also reaching plugins (`core/`):** - `core.viz`, graphics for live numbers: `line`, `bar`, `stat`, `Table` (#345); `Pie`, `keyColor`, `palette` (#347); the table's rounded header and shaded rows (#359). - `core.profile`: - `lookback()`: 30 s of costs and the frames over budget, plus `ReportOptions.over_ms` / `.hitches` and `selfTimes` (#353). - `frameCauses()` and `ReportOptions.causes`: why each frame happened (#364). - `phase` / `Phases` / `ReportOptions.startup`: startup timing (#365, which landed inside #364's squash commit). - The profiler's `abi` stays 4. ## Verified - [x] macOS: - `zig build` and `test-sdk-version` on this head. - `scripts/pack-sdk.sh` packs `fizzy-sdk-v0.2.23.tar.gz`. - Every store plugin (fresh pulls of pixi, zig, drive, atlas and ghostty `main`) builds against this tree's `sdk/` with `zig build --fork=<sdk>` into a throwaway profile (31/31, and 28/28 for each of the other four). The fingerprint moved, so each repin PR is a real rebuild, but none should open as a draft for a broken build. The agent and chat plugins build too (22/22, 19/19). - [ ] Windows: - [ ] Linux: CI. - [ ] Web: ## Follow-ups - Each repin PR merged and its plugin tagged (the plugin repos' tags stay the maintainer's). - `fizzyedit/agent` and `fizzyedit/chat` repin to the `sdk-v0.2.23` tarball. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of
plans/RESTART_AND_CRASHES_PLAN.md, step 3 (crash reports), first PR.What changes
When fizzy crashes, it says whose code crashed. The report names fizzy itself or the plugin, with versions, every loaded module and each stack frame as a module plus an offset. It goes to stderr and to
<config>/crashes/<launch ms>-<pid>.txt. A plugin crash in a ReleaseFast build, from a sandbox run:Before this, a release build had no fault handler at all: a crash printed nothing.
What reaches it (
app/crash/crash.zig):FullPanic(crash.panic).enable_segfault_handler), routed throughroot.debug.handleSegfault. Covers SIGSEGV/BUS/ILL/FPE and Windows access violations.abort().After the report, each one hands on to what ran before: std's handler, which still prints the symbolized trace (the executable isn't stripped), or the signal's default action. Only one report is written per run, so fizzy's own panic doesn't also report the abort it ends in.
The module table: a fixed table of 64 slots.
main.PluginLoader.registerbefore itsregisterruns, and removed inunloadPluginbeforedlclose.app/crash/image.zig): the Mach-OLC_UUID, the ELFNT_GNU_BUILD_IDnote, or the PE CodeView GUID+age. A crash only compares addresses.dladdr.Build ids on ELF: Zig's linker writes a build id only when asked. The executable and plugins built for ELF now set
build_id = .fast(build/exe.zig,sdk/plugin_sdk.zig).Writing allocates nothing: the file is named once the config folder is known (
crash.writeTo, inEditor.init), and a crash writes it withopen/write(CreateFileW/WriteFileon Windows) from a static buffer. A crash before that point reports to stderr only. A run that doesn't crash leaves no file.Cost: none per frame. The handlers are installed once, and each plugin load does one table write. std already attaches the per-thread alternate signal stack whenever
signal_stack_sizeis set, which is the default, so turning the handler on in release builds adds nothing there.crashis a std-only module of its own (build/sdk.zig'scrashModule), so the plugin-loader test, which is rooted atPluginLoader.zig, can reach it. The app re-exports it asapp.crash.SDK impact
plugin_sdk.ziggives ELF plugins a build id; no fingerprint move (test-sdk-versionpasses).Verified
registeron request:crashed in crashy 0.3.1. Eachcrashy+0x…offset matches std's symbolized trace (crashy+0x1b9404=0x12ba11404 in deep).crashed in crashy 0.3.1: Abort, with frames fromlibsystem_cinto crashy.bufPrintinwriteTo, fixed). The report namedfizzywith its frames, and std's trace followed.kill -SEGVwhile idle (ReleaseFast): the report shows system frames (kernel, CoreFoundation, HIToolbox, AppKit) named by image, then fizzy's.kill -TERM: a normal exit, no report file.zig build;zig build test(85/85 steps, 525/526 tests, 1 skipped), including the newfizzy-crash-tests(report text, image lookup, build-id note parsing);test-integration(44/44 steps, 359/359);check-web;test-sdk-version.scripts/check-examples.shbuilt example-plugin against this tree; its example-app half fails locally on macOS (the script writes an absolute temp path into the zon, which Zig rejects), unrelated to this change, so that half is CI's.x86_64-linux-gnu, handlers included (refAllDecls).x86_64-windows-gnu.check-web; nothing is installed there (crash.supportedis false).Follow-ups
panicthat forwards to the host'scrash.panicthrough a symbol the executable exports. This also covers Windows, where a plugin's panic ends inRtlExitUserProcess(3), which no handler sees, so today it writes no report.🤖 Generated with Claude Code