Saving fuzzer crash files as reproductions

When the fuzzer finds a crash it saves a file to .zig-cache/f/crash and seems to continue fuzzing. Is that true? If so, are crash files overwritten on new crashes?

I’d like to automate saving the crash files on each crash so they can be run as tests. Has anyone else has done similar?

EDIT

I’ve been doing the following and it gives me reproductions.

fn fuzzFile(io: std.Io, path: []const u8) !void {
    const contents = std.Io.Dir.cwd().readFileAlloc(io, path, testgpa, .unlimited) catch return;
    defer testgpa.free(contents);
    var smith = testing.Smith{ .in = contents };
    try fuzz1(&smith, testgpa);
}

test "fuzz crash" {
    try fuzzOracleFile(testing.io, ".zig-cache/f/crash");
}

@gooncreeper any thoughts on how to automate saving the crash files or any of this?

I recommend symlinking (or copying every time) the crash file into the source directory and doing something akin to the following while testing:

test "fuzz test" {
    try std.testing.fuzz(fuzzCtx, fuzzFn, .{
        .corpus = &.{ @embedFile("crash") },
    });
}

I also usually take advantage of @import("builtin").fuzz to print a human-readable input when rerunning in debug mode. Note that you will see two runs, one with your actual case and one with an empty input.

To answer your first question, the fuzzer does not continue running once it finds a crash, though the build system continues running for the webui.

Thanks! I didn’t think to provide a corpus. I’ll have to build up the corpus contents from files in my testdata/crashes folder.

Will also look into `@import(“builtin”).fuzz and human readable input.

Good to know this. Makes sense now whats happening. I guess we’re limited to one crash per fuzzing run. Hopefully we can continue fuzzing with some kind of strategy for handling multiple crash files in the future.

Thanks for your work on the fuzzer! I’m getting a lot of mileage out of it as-is running for a bit and copying files manually.

Heya! Thanks @gooncreeper, this has been working for me on my main machine (x86_64 Linux). However, just noticed this isn’t working for me on my M3 MacBook Air.

I’ve pushed a commit with a fuzzer crash corpus here.

Running this on my main machine looks like this:

fng@appa ~/src/edit (fuzzer-repro)
❯ git log -1
commit 4d1536a6626708d7b1591960399f7cfe0770584d (HEAD -> fuzzer-repro, origin/fuzzer-repro)
Author: Francisco Nevitt Gonçalves <git@francisco.wiki>
Date:   Sun Oct 4 00:16:43 2026 +0100

    Fuzzer crash corpus
fng@appa ~/src/edit (fuzzer-repro)
❯ zig build test
anyzig: .minimum_zig_version '0.17.0' pulled from '/home/fng/src/edit/build.zig.zon'
anyzig: appdata '/home/fng/.local/share/anyzig'
anyzig: zig '0.17.0' already exists at '/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP'
test
└─ run test 41 pass, 1 crash (42 total)
error: 'editor.test.fuzzEditor repro2' terminated with signal ABRT with stderr:
       thread 16483 panic: integer overflow
       /home/fng/src/edit/editor.zig:733:56: 0x126f9bf in lastOffset (editor.zig)
               return viewport.line_offset_start + text_width - 1;
                                                              ^
       /home/fng/src/edit/editor.zig:316:55: 0x126f3e2 in focus (editor.zig)
               const last_offset = editor.viewport.lastOffset();
                                                             ^
       /home/fng/src/edit/editor.zig:288:25: 0x125ce63 in tick (editor.zig)
               try editor.focus(cursor_position);
                               ^
       /home/fng/src/edit/editor.zig:1455:28: 0x127c551 in fuzzEditor (editor.zig)
               if (ctx.editor.tick(event) catch |err| switch (err) {
                                  ^
       /home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/compiler/test_runner.zig:620:20: 0x127bcfd in fuzz__func_1049 (test_runner.zig)
               try testOne(context, &smith);
                          ^
       /home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/std/testing.zig:1262:32: 0x127bade in fuzz (editor.zig)
           return @import("root").fuzz(context, testOne, options);
                                      ^
       /home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/compiler/test_runner.zig:143:68: 0x1203565 in mainServer (test_runner.zig)
                       const status: TestResults.Status = if (test_fn.func()) |v| s: {
                                                                          ^
       /home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/compiler/test_runner.zig:72:26: 0x12043cd in main (test_runner.zig)
               return mainServer(init) catch |err| panic("internal test runner failure: {t}", .{err});
                                ^
       /home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/std/start.zig:789:88: 0x1200bbf in callMain (std.zig)
           if (fn_info.param_types[0].? == std.process.Init.Minimal) return wrapMain(root.main(.{
                                                                                              ^
       /home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/std/start.zig:248:5: 0x12005a1 in _start (std.zig)
           asm volatile (switch (native_arch) {
           ^
failed command: ./.zig-cache/o/595d89642699ce0df2b781790fdf55fe/test --cache-dir=./.zig-cache --seed=0x7df054ec --listen=-

Build Summary: 4/6 steps succeeded (1 failed); 41/42 tests passed (1 crashed)
test transitive failure
└─ run test 41 pass, 1 crash (42 total)

On my MacBook, running zig build test on the same commit passes. I’m using anyzig so both machines are using the same Zig version. When reproducing a fuzzing failure found on my MacBook, the same thing happens. The tests just pass. Is this a known issue? (Sorry, Codeberg hasn’t let me search the issue tracker all day.)

I looked through your code and did not see anything that would be causing target-dependent results. Smith should give the same values regardless of the target. Still, you could change editor.io to use std.Io.failing to be sure that it is unused. If you want to make sure its not Smith returning different values you could print them to make sure they are the same on both machines. The input on the repro should be row_count = 5, col_count = 3, then .{.ascii = 'G'}.

Thanks a lot for having a look!

Just been looking into this again this morning. I think there’s something wrong with the crash files produced on my M3 MacBook Air. Crashes produced on this machine do not reproduce on this machine or my other laptop (an old Intel MacBook Pro running Fedora). However, crashes produced on the MBP do repro on the MBP and the MBA.

Here’s what I tried (this branch):

First, I ran the fuzzer on the MBA (aarch64-darwin) and tried to reproduce it with the crash file. It didn't reproduce the crash (end of code block). I committed and pushed this file to try on my other laptop.
fng@mba ~/src/edit (crash-repro)
❯ git log -1
commit 59675bb552e8a990bca8f3e2e9073d91b8a9580e (HEAD -> crash-repro, origin/crash-repro)
Author: Francisco Nevitt Gonçalves <git@francisco.wiki>
Date:   Tue Oct 6 08:16:05 2026 +0100

    Repro from @embedFile("crash")
fng@mba ~/src/edit (crash-repro)
❯ zig build test --fuzz -Doptimize=ReleaseSafe
anyzig: .minimum_zig_version '0.17.0' pulled from '/Users/fng/src/edit/build.zig.zon'
anyzig: appdata '/Users/fng/Library/Application Support/anyzig'
anyzig: zig '0.17.0' already exists at '/Users/fng/.cache/zig/p/N-V-__8AANXWIBcbY8pXMVo7nWkTYbdlzcA0Fkfs8goGAzgS'
info(web_server): web interface listening at http://[::1]:49366/
info(web_server): hint: pass '--webui=[::1]:49366' to use the same port next time
Build Summary: 8/8 steps succeeded; 39/39 tests passed
test success
├─ run test 39 pass (39 total) 2ms MaxRSS:1M
└─ install success

test
└─ run test failure
thread 4474444 panic: reached unreachable code
/Users/fng/.cache/zig/p/N-V-__8AANXWIBcbY8pXMVo7nWkTYbdlzcA0Fkfs8goGAzgS/lib/std/debug.zig:442:14: 0x10450eff3 in assert (test)
    if (!ok) unreachable; // assertion failure
             ^
/Users/fng/src/edit/src/editor.zig:289:26: 0x104511723 in tick (test)
        try editor.render(cursor_position);
                         ^
/Users/fng/src/edit/src/editor.zig:1442:28: 0x1045bb0a7 in fuzzEditor (test)
        if (ctx.editor.tick(event) catch |err| switch (err) {
                           ^
/Users/fng/.local/zig-0.17.0/lib/fuzzer.zig:1356:32: 0x104501473 in run (fuzzer)
        const skip = f.test_one();
                               ^
/Users/fng/.local/zig-0.17.0/lib/fuzzer.zig:1485:28: 0x1044ff5cf in batch (fuzzer)
                    f.cycle();
                           ^
/Users/fng/.cache/zig/p/N-V-__8AANXWIBcbY8pXMVo7nWkTYbdlzcA0Fkfs8goGAzgS/lib/compiler/test_runner.zig:612:35: 0x1045bacaf in fuzz__func_933 (test)
        fuzz_abi.fuzzer_start_test();
                                  ^
/Users/fng/.cache/zig/p/N-V-__8AANXWIBcbY8pXMVo7nWkTYbdlzcA0Fkfs8goGAzgS/lib/compiler/test_runner.zig:451:60: 0x1045afecf in runner_test_run (test)
        builtin.test_functions[fuzz_runner.indexes[i]].func() catch |err| switch (err) {
                                                           ^
/Users/fng/.local/zig-0.17.0/lib/fuzzer.zig:2240:24: 0x104507653 in runTest (fuzzer)
    abi.runner_test_run(i);
                       ^
/Users/fng/.cache/zig/p/N-V-__8AANXWIBcbY8pXMVo7nWkTYbdlzcA0Fkfs8goGAzgS/lib/compiler/test_runner.zig:244:37: 0x1045a6df3 in mainServer (test)
                fuzz_abi.fuzzer_main(n_tests, testing.random_seed, mode, amount_or_instance);
                                    ^
/Users/fng/.cache/zig/p/N-V-__8AANXWIBcbY8pXMVo7nWkTYbdlzcA0Fkfs8goGAzgS/lib/compiler/test_runner.zig:72:26: 0x1045a53bb in main (test)
        return mainServer(init) catch |err| panic("internal test runner failure: {t}", .{err});
                         ^
???:?:?: 0x182e044e3 in start (/usr/lib/dyld)
error: test 'editor.decltest.fuzzEditor' terminated with signal ABRT; input saved to '.zig-cache/f/crash'
failed command: ./.zig-cache/o/18a7e3045b9ba6359dbd680fb3781525/test --cache-dir=./.zig-cache --seed=0x3567f0c0 --listen=-

^C⏎
fng@mba ~/src/edit (crash-repro) [130]
❯ cp .zig-cache/f/crash src/crash
fng@mba ~/src/edit (crash-repro)
❯ cat .zig-cache/f/crash
PMYP,A:8A:ap,�de�Brp:�wE$bu�d�wIb�Wwb�^�ked:�-%Xd$b⏎
fng@mba ~/src/edit (crash-repro)
❯ git status
On branch crash-repro
Your branch is up to date with 'origin/crash-repro'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        src/crash

nothing added to commit but untracked files present (use "git add" to track)
fng@mba ~/src/edit (crash-repro)
❯ git add src/crash
fng@mba ~/src/edit (crash-repro)
❯ git commit -m "Add crash"
[crash-repro fe16e84] Add crash
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 src/crash
fng@mba ~/src/edit (crash-repro)
❯ git push
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 566 bytes | 566.00 KiB/s, done.
Total 4 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:fng97/edit.git
   59675bb..fe16e84  crash-repro -> crash-repro
fng@mba ~/src/edit (crash-repro)
❯ zig build repro
anyzig: .minimum_zig_version '0.17.0' pulled from '/Users/fng/src/edit/build.zig.zon'
anyzig: appdata '/Users/fng/Library/Application Support/anyzig'
anyzig: zig '0.17.0' already exists at '/Users/fng/.cache/zig/p/N-V-__8AANXWIBcbY8pXMVo7nWkTYbdlzcA0Fkfs8goGAzgS'
fng@mba ~/src/edit (crash-repro)
❯
It didn't reproduce on my MBP (x86_64-linux) either. As a sanity check, you can see that the same crash file is printed in the code block above and in this one.
fng@mbp ~/src/edit (crash-repro)
❯ git log -1
commit fe16e8448d38049de4e34a40e011130e818810a6 (HEAD -> crash-repro, origin/crash-repro)
Author: Francisco Nevitt Gonçalves <git@francisco.wiki>
Date:   Tue Oct 6 08:17:52 2026 +0100

    Add crash
fng@mbp ~/src/edit (crash-repro)
❯ cat src/crash
PMYP,A:8A:ap,�de�Brp:�wE$bu�d�wIb�Wwb�^�ked:�-%Xd$b⏎
fng@mbp ~/src/edit (crash-repro)
❯ zig build repro
fng@mbp ~/src/edit (crash-repro)
❯
However, a crash generated on my MBP does repro there and...
fng@mbp ~/src/edit (crash-repro)
❯ git status
On branch crash-repro
Your branch is up to date with 'origin/crash-repro'.

nothing to commit, working tree clean
fng@mbp ~/src/edit (crash-repro)
❯ git log -1
commit fe16e8448d38049de4e34a40e011130e818810a6 (HEAD -> crash-repro, origin/crash-repro)
Author: Francisco Nevitt Gonçalves <git@francisco.wiki>
Date:   Tue Oct 6 08:17:52 2026 +0100

    Add crash
fng@mbp ~/src/edit (crash-repro)
❯ zig build test --fuzz -Doptimize=ReleaseSafe
info(web_server): web interface listening at http://[::1]:35711/
info(web_server): hint: pass '--webui=[::1]:35711' to use the same port next time
test
└─ run test failure
thread 5662 panic: index out of bounds: index 21, len 6
/home/fng/src/edit/src/editor.zig:824:26: 0x106227e in slice (test)
            return buffer[s.head .. s.tail + 1];
                         ^
/home/fng/src/edit/src/editor.zig:141:40: 0x106336f in tick (test)
                        try editor.yank();
                                       ^
/home/fng/src/edit/src/editor.zig:1442:28: 0x10ef2e8 in fuzzEditor (test)
        if (ctx.editor.tick(event) catch |err| switch (err) {
                           ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/fuzzer.zig:1356:32: 0x115f190 in run (fuzzer)
        const skip = f.test_one();
                               ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/fuzzer.zig:1485:28: 0x115cdf4 in batch (fuzzer)
                    f.cycle();
                           ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/compiler/test_runner.zig:612:35: 0x10eee0c in fuzz__func_896 (test)
        fuzz_abi.fuzzer_start_test();
                                  ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/compiler/test_runner.zig:451:60: 0x10e2794 in runner_test_run (test)
        builtin.test_functions[fuzz_runner.indexes[i]].func() catch |err| switch (err) {
                                                           ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/fuzzer.zig:2240:24: 0x1166387 in runTest (fuzzer)
    abi.runner_test_run(i);
                       ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/compiler/test_runner.zig:244:37: 0x10dc6e2 in mainServer (test)
                fuzz_abi.fuzzer_main(n_tests, testing.random_seed, mode, amount_or_instance);
                                    ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/compiler/test_runner.zig:72:26: 0x10dc923 in main (test)
        return mainServer(init) catch |err| panic("internal test runner failure: {t}", .{err});
                         ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/std/start.zig:789:88: 0x10dabde in callMain (test)
    if (fn_info.param_types[0].? == std.process.Init.Minimal) return wrapMain(root.main(.{
                                                                                       ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/std/start.zig:248:5: 0x10da78d in _start (test)
    asm volatile (switch (native_arch) {
    ^
error: test 'editor.decltest.fuzzEditor' terminated with signal ABRT; input saved to '.zig-cache/f/crash'
failed command: ./.zig-cache/o/8b7c5ea795d471e5d974128ca46e836b/test --cache-dir=./.zig-cache --seed=0xb90bc405 --listen=-

Build Summary: 8/8 steps succeeded; 39/39 tests passed
test success
├─ run test 39 pass (39 total) 4s MaxRSS:20M
└─ install success
   ├─ install edit success
   └─ install fuzz_repro success

^C⏎
fng@mbp ~/src/edit (crash-repro) [130]
❯ cp .zig-cache/f/crash src/crash
fng@mbp ~/src/edit (crash-repro)
❯ cat src/crash
<

 �:8EdjBIs_bEW�e^be��wR�:w�O��l%`�vC�a�[�v�Z���:wB�55p9�d⏎
fng@mbp ~/src/edit (crash-repro)
❯ zig build repro
thread 5773 panic: index out of bounds: index 21, len 6
/home/fng/src/edit/src/editor.zig:824:26: 0x121e9c6 in slice (repro.zig)
            return buffer[s.head .. s.tail + 1];
                         ^
/home/fng/src/edit/src/editor.zig:543:71: 0x1202d86 in yank (repro.zig)
            editor.clipboard.appendSliceAssumeCapacity(selection.slice(editor.document.items))
                                                                      ^
/home/fng/src/edit/src/editor.zig:141:40: 0x12080c9 in tick (repro.zig)
                        try editor.yank();
                                       ^
/home/fng/src/edit/src/editor.zig:1442:28: 0x120af81 in fuzzEditor (repro.zig)
        if (ctx.editor.tick(event) catch |err| switch (err) {
                           ^
/home/fng/src/edit/src/repro.zig:24:19: 0x120b457 in main (repro.zig)
    try fuzzEditor(&ctx, &smith);
                  ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/std/start.zig:827:30: 0x120bf28 in callMain (std.zig)
    return wrapMain(root.main(.{
                             ^
/home/fng/.cache/zig/p/N-V-__8AALBXTRYsilzupvrmv0afm7ohtj5Mj0paqVfKLmfP/lib/std/start.zig:248:5: 0x1202641 in _start (std.zig)
    asm volatile (switch (native_arch) {
    ^
repro
└─ run exe fuzz_repro failure
error: process terminated with signal ABRT
failed command: ./zig-out/bin/fuzz_repro

Build Summary: 5/7 steps succeeded (1 failed)
repro transitive failure
└─ run exe fuzz_repro failure

fng@mbp ~/src/edit (crash-repro) [1]
❯ git status
On branch crash-repro
Your branch is up to date with 'origin/crash-repro'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
modified:   src/crash

no changes added to commit (use "git add" and/or "git commit -a")
fng@mbp ~/src/edit (crash-repro)
❯ git commit -a -m "Add crash from MBP (x86_64-linux)"
[crash-repro 48d9d79] Add crash from MBP (x86_64-linux)
 1 file changed, 0 insertions(+), 0 deletions(-)
It repros on my MBA too.
fng@mba ~/src/edit (crash-repro)
❯ git status
On branch crash-repro
Your branch is up to date with 'origin/crash-repro'.

nothing to commit, working tree clean
fng@mba ~/src/edit (crash-repro)
❯ git log -1
commit 48d9d796dad0e629064b3d11bb3d3ca63317d3a9 (HEAD -> crash-repro, origin/crash-repro)
Author: Francisco Nevitt Gonçalves <git@francisco.wiki>
Date:   Tue Oct 6 08:35:36 2026 +0100

    Add crash from MBP (x86_64-linux)
fng@mba ~/src/edit (crash-repro)
❯ zig build repro
anyzig: .minimum_zig_version '0.17.0' pulled from '/Users/fng/src/edit/build.zig.zon'
anyzig: appdata '/Users/fng/Library/Application Support/anyzig'
anyzig: zig '0.17.0' already exists at '/Users/fng/.cache/zig/p/N-V-__8AANXWIBcbY8pXMVo7nWkTYbdlzcA0Fkfs8goGAzgS'
thread 4505084 panic: index out of bounds: index 21, len 6
/Users/fng/src/edit/src/editor.zig:824:26: 0x1043dc127 in slice (fuzz_repro)
            return buffer[s.head .. s.tail + 1];
                         ^
/Users/fng/src/edit/src/editor.zig:543:71: 0x1043cba6f in yank (fuzz_repro)
            editor.clipboard.appendSliceAssumeCapacity(selection.slice(editor.document.items))
                                                                      ^
/Users/fng/src/edit/src/editor.zig:141:40: 0x1043ced1f in tick (fuzz_repro)
                        try editor.yank();
                                       ^
/Users/fng/src/edit/src/editor.zig:1442:28: 0x1043d0747 in fuzzEditor (fuzz_repro)
        if (ctx.editor.tick(event) catch |err| switch (err) {
                           ^
/Users/fng/src/edit/src/repro.zig:24:19: 0x1043d09c3 in main (fuzz_repro)
    try fuzzEditor(&ctx, &smith);
                  ^
/Users/fng/.cache/zig/p/N-V-__8AANXWIBcbY8pXMVo7nWkTYbdlzcA0Fkfs8goGAzgS/lib/std/start.zig:827:30: 0x1043d0f2f in callMain (fuzz_repro)
    return wrapMain(root.main(.{
                             ^
???:?:?: 0x182e044e3 in start (/usr/lib/dyld)
repro
└─ run exe fuzz_repro failure
error: process terminated with signal ABRT
failed command: ./zig-out/bin/fuzz_repro

Build Summary: 5/7 steps succeeded (1 failed)
repro transitive failure
└─ run exe fuzz_repro failure

Going to look into this a bit more but wanted to share this first in case you have any ideas.

Also, thanks for the std.Io.failing suggestion. Exactly what I was looking for!