# Thread panic when returning an error after initializing GLFW

**URL:** <https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567>\
**Category:** Help\
**Tags:** c, zig-0-16\
**Created:** [July 7, 2026, 9:55pm UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567 "2026-07-07T21:55:03Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![ThomasPais](https://ziggit.dev/user_avatar/ziggit.dev/thomaspais/32/9440_2.png) [@ThomasPais](https://ziggit.dev/u/ThomasPais)\
**Post date:** [July 7, 2026, 9:55pm UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567/1 "2026-07-07T21:55:03Z")

</div>

Hi, I ran into an issue using GLFW with Zig 0.16.0 on Gentoo (Wayland/niri).

I first hit this in a bigger project, where I could open a window and render OpenGL quads fine, so GLFW itself is working. The panic only shows up when an error propagates out of `main` unhandled. Here’s the smallest repro I could get it down to:

**main.zig**

```zig
const std = @import("std");
const c = @cImport({
    @cInclude("GLFW/glfw3.h");
});

pub fn main(init: std.process.Init) !void {
    _ = init;
    if (c.glfwInit() == 0) return error.GlfwInitFailed;
    defer c.glfwTerminate();

    return error.DeliberateTestError;
}

```

**build.zig**

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "game",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .link_libc = true,
        }),
    });

    exe.root_module.linkSystemLibrary("glfw", .{});

    b.installArtifact(exe);

    const run_step = b.step("run", "Run the application");
    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step);

    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }
}

```

Here is the command I used to run the program:

```zig
$ zig build run
thread 19458 panic: attempt to use null value
error return context:

```

Then it just hangs, has to be killed.

If I remove the `return error.DeliberateTestError;` line so `main` exits normally, no problem at all. If I keep the error but don’t call `glfwInit()`, also no problem and it prints the error trace and exits. So `glfwInit()` specifically seems to be the thing that triggers it, not window/context creation or anything that comes normally after.

Backtrace from gdb:

```zig
$ gdb --args zig-out/bin/game 
GNU gdb (Gentoo 17.2 vanilla) 17.2
Copyright (C) 2025 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://bugs.gentoo.org/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from zig-out/bin/game...
(gdb) run
Starting program: /home/thomas/Projects/Games/balian/zig-out/bin/game 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib64/libthread_db.so.1".
[New Thread 0x7ffff5f016c0 (LWP 19707)]
[New Thread 0x7ffff57006c0 (LWP 19708)]
[New Thread 0x7ffff4eff6c0 (LWP 19709)]
[New Thread 0x7fffe7fff6c0 (LWP 19710)]
[New Thread 0x7fffe77fe6c0 (LWP 19711)]
thread 19703 panic: attempt to use null value
error return context:
^C
Thread 1 "game" received signal SIGINT, Interrupt.
0x00000000010db8eb in os.linux.x86_64.syscall4 (number=futex, arg1=19318312, arg2=128, arg3=2, arg4=0)
(gdb) bt
#0 0x00000000010db8eb in os.linux.x86_64.syscall4 ()
#1 0x00000000010db4cf in os.linux.futex_4arg ()
#2 0x00000000010dab54 in Io.Threaded.Thread.futexWaitInner ()
#3 0x00000000010d890f in Io.Threaded.Thread.futexWaitUncancelable ()
#4 0x00000000010d87f7 in Io.Threaded.mutexLock ()
#5 0x00000000010d590d in Io.Threaded.scanEnviron ()
#6 0x00000000010d5a15 in Io.Threaded.environString__anon_13417 ()
#7 0x00000000010d0734 in debug.ElfFile.DebugInfoSearchPaths.native ()
#8 0x00000000010bddee in debug.SelfInfo.Elf.Module.loadElf ()
#9 0x00000000010bd694 in debug.SelfInfo.Elf.Module.getLoadedElf ()
#10 0x000000000103dfa1 in debug.SelfInfo.Elf.getSymbols ()
#11 0x00000000010272cf in debug.printSourceAtAddress ()
#12 0x00000000011241d1 in debug.writeTrace ()
#13 0x0000000001123c89 in debug.writeErrorReturnTrace ()
#14 0x000000000102521f in debug.defaultPanic ()
#15 0x000000000102ffa4 in debug.FullPanic((function 'defaultPanic')).unwrapNull ()
#16 0x00000000010d5ad6 in Io.Threaded.Environ.scan ()
#17 0x00000000010d597f in Io.Threaded.scanEnviron ()
#18 0x0000000001164c30 in Io.Threaded.initLockedStderr ()
#19 0x00000000011653fc in Io.Threaded.lockStderr ()
#20 0x00000000011263ff in Io.lockStderr ()
#21 0x00000000011260c5 in debug.lockStderr ()
#22 0x00000000011d25d5 in log.defaultLog__anon_35555 ()
#23 0x00000000011d24cb in log.log__anon_35548 ()
#24 0x00000000011d246b in log.scoped(.default).err__anon_32808 ()
#25 0x00000000011ccb51 in start.main ()
#26 0x00007ffff7cd21fe in ??? () at /usr/lib64/libc.so.6
#27 0x00007ffff7cd231b in __libc_start_main () at /usr/lib64/libc.so.6
#28 0x00000000012042b5 in _start ()
(gdb) 

```

I also tested with the official `zig-x86_64-linux-0.16.0` binary from [ziglang.org](http://ziglang.org) (not just my Gentoo-built one), same exact panic and backtrace, so it doesn’t look like a packaging issue on my end.

Let me know if there’s anything else I can test. Thanks in advance!

---

<div class="post-metadata">

**Author:** ![ForeverZer0](https://ziggit.dev/user_avatar/ziggit.dev/foreverzer0/32/2443_2.png) [@ForeverZer0](https://ziggit.dev/u/ForeverZer0)\
**Post date:** [July 8, 2026, 12:38am UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567/2 "2026-07-08T00:38:25Z")

</div>

Copy-pasted your exact code, though did add `exe.use_llvm = true;` in `build.zig` (there is some issue when linking with libc).

Running on Arch/Hyprland (Zig 0.16) works as expected.

```zig
❯ zig build run
error: DeliberateTestError
/home/eric/zig/temp/src/main.zig:11:5: 0x11725d5 in main (game)
    return error.DeliberateTestError;
    ^
run
└─ run exe game failure
error: process exited with error code 1
failed command: /home/eric/zig/temp/zig-out/bin/game

Build Summary: 3/5 steps succeeded (1 failed)
run transitive failure
└─ run exe game failure

error: the following build command failed with exit code 1:
.zig-cache/o/a2d9c775799e82cbd685e268d70660c6/build /home/eric/.local/share/zvm/0.16.0/zig /home/eric/.l
ocal/share/zvm/0.16.0/lib /home/eric/zig/temp .zig-cache /home/eric/.cache/zig --seed 0x80ddb840 -Zdf98d
c787cea2bd3 run

```

It really doesn’t appear to be anything with your code, nothing amiss there aside from using the deprecated `@cImport`.

---

<div class="post-metadata">

**Author:** ![ThomasPais](https://ziggit.dev/user_avatar/ziggit.dev/thomaspais/32/9440_2.png) [@ThomasPais](https://ziggit.dev/u/ThomasPais)\
**Post date:** [July 8, 2026, 6:09pm UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567/3 "2026-07-08T18:09:38Z")

</div>

Thanks for your reply! I was not aware that `@cImport` was deprecated.

Adding `exe.root_module.use_llvm = true` did not fix it for me.

I tried to use the new `translate_c`:

**build.zig**

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const translate_c = b.addTranslateC(.{
        .root_source_file = b.path("vendors/c.h"),
        .target = target,
        .optimize = optimize,
        // .link_libc = true,
    });
    translate_c.linkSystemLibrary("glfw", .{});

    const exe = b.addExecutable(.{
        .name = "game",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{
                    .name = "c",
                    .module = translate_c.createModule(),
                },
            },
        }),
    });
    // exe.use_llvm = true;
    b.installArtifact(exe);

    const run_step = b.step("run", "Run the application");
    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step);

    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }
}

```

**c.h**

```zig
#include <GLFW/glfw3.h>

```

**main.zig**

```zig
const std = @import("std");
const c = @import("c");

pub fn main(init: std.process.Init) !void {
    _ = init;
    if (c.glfwInit() == 0) return error.GlfwInitFailed;
    errdefer c.glfwTerminate();

    return error.DeliberateTestError;
}

```

Adding or removing `use_llvm` or `link_libc` does not change anything.

I still get the same result:

```zig
$ zig build run
thread 12595 panic: attempt to use null value
error return context:

```

Once again, the code run fine when `main` do not return an error.

I have a windows machine, i will try to test this code on it…

---

<div class="post-metadata">

**Author:** ![ForeverZer0](https://ziggit.dev/user_avatar/ziggit.dev/foreverzer0/32/2443_2.png) [@ForeverZer0](https://ziggit.dev/u/ForeverZer0)\
**Post date:** [July 8, 2026, 7:26pm UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567/4 "2026-07-08T19:26:20Z")

</div>

> [@ThomasPais](#):
>
> Adding `exe.root_module.use_llvm = true` did not fix it for me.

No, I didn’t expect so, was just clarifying that I did make one modification to your example, but this is because of an [unrelated linking issue](https://ziggit.dev/t/linker-error-when-building-zig-from-source/14394).

My point being is that I don’t think your problem is related to your code or what you are doing in Zig, but something else in the environment. I apologize, I know this is not very helpful in narrowing it down, but it does show that your code runs normally _outside_ of your environment.

---

<div class="post-metadata">

**Author:** ![ThomasPais](https://ziggit.dev/user_avatar/ziggit.dev/thomaspais/32/9440_2.png) [@ThomasPais](https://ziggit.dev/u/ThomasPais)\
**Post date:** [July 8, 2026, 7:34pm UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567/5 "2026-07-08T19:34:29Z")

</div>

That actually helps, thank you! Knowing that the exact same code runs normally on your end confirms that my Zig code isn’t the problem here.

For now, I think I will switch over to using the package from [GitHub - tiawl/glfw.zig: @glfw packaged for @ziglang · GitHub](https://github.com/tiawl/glfw.zig)…

If I manage to pin down exactly what is broken in my Gentoo/Wayland environment later on, I will make sure to update this thread with the solution.

---

<div class="post-metadata">

**Author:** ![ThomasPais](https://ziggit.dev/user_avatar/ziggit.dev/thomaspais/32/9440_2.png) [@ThomasPais](https://ziggit.dev/u/ThomasPais)\
**Post date:** [July 8, 2026, 10:10pm UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567/6 "2026-07-08T22:10:54Z")

</div>

I tested my code on Windows, and it worked correctly. Returning an error from `main` does not cause a panic there, so this confirms again that the issue is related to my environment rather than the code itself.

My Gentoo setup is probably too minimal and may be missing something required at runtime 😅

I also tried using the packaged GLFW from _tiawl_, but I got the same result. It’s strange because I can render quads with OpenGL, handle the window, and process input correctly, yet returning an error still causes the issue…

---

<div class="post-metadata">

**Author:** ![ThomasPais](https://ziggit.dev/user_avatar/ziggit.dev/thomaspais/32/9440_2.png) [@ThomasPais](https://ziggit.dev/u/ThomasPais)\
**Post date:** [July 11, 2026, 9:00pm UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567/7 "2026-07-11T21:00:58Z")

</div>

I’ve done some more digging and can confirm it’s also happening on my NixOS PC. This is not a minimal system like my Gentoo one.

I ran the same test using SDL3 instead, and it works perfectly:

```zig
const std = @import("std");
const c = @import("c");

pub fn main(init: std.process.Init) !void {
    _ = init;
    if (c.SDL_Init(c.SDL_INIT_VIDEO) == false) return error.SDLInitFailed;
    defer c.SDL_Quit();

    return error.DeliberateTestError;
}

```

```zig
$ ./zig-out/bin/bal 
error: DeliberateTestError
/home/thomas/Projects/Games/bal/src/main.zig:12:5: 0x11cd33f in main (main.zig)
    return error.DeliberateTestError;
    ^

```

Since the SDL3 path works, I think I’m just going to move over to that for this project but if anyone has any ideas on what might be causing this, I’d still love to know!

---

<div class="post-metadata">

**Author:** ![castholm](https://ziggit.dev/user_avatar/ziggit.dev/castholm/32/1330_2.png) [@castholm](https://ziggit.dev/u/castholm)\
**Post date:** [July 11, 2026, 9:32pm UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567/8 "2026-07-11T21:32:45Z")

</div>

> [@ThomasPais](#):
>
> ```zig
> #0 0x00000000010db8eb in os.linux.x86_64.syscall4 ()
> #1 0x00000000010db4cf in os.linux.futex_4arg ()
> #2 0x00000000010dab54 in Io.Threaded.Thread.futexWaitInner ()
> #3 0x00000000010d890f in Io.Threaded.Thread.futexWaitUncancelable ()
> #4 0x00000000010d87f7 in Io.Threaded.mutexLock ()
> #5 0x00000000010d590d in Io.Threaded.scanEnviron ()
> #6 0x00000000010d5a15 in Io.Threaded.environString__anon_13417 ()
> #7 0x00000000010d0734 in debug.ElfFile.DebugInfoSearchPaths.native ()
> #8 0x00000000010bddee in debug.SelfInfo.Elf.Module.loadElf ()
> #9 0x00000000010bd694 in debug.SelfInfo.Elf.Module.getLoadedElf ()
> #10 0x000000000103dfa1 in debug.SelfInfo.Elf.getSymbols ()
> #11 0x00000000010272cf in debug.printSourceAtAddress ()
> #12 0x00000000011241d1 in debug.writeTrace ()
> #13 0x0000000001123c89 in debug.writeErrorReturnTrace ()
> #14 0x000000000102521f in debug.defaultPanic ()
> #15 0x000000000102ffa4 in debug.FullPanic((function 'defaultPanic')).unwrapNull ()
> #16 0x00000000010d5ad6 in Io.Threaded.Environ.scan ()
> #17 0x00000000010d597f in Io.Threaded.scanEnviron ()
> #18 0x0000000001164c30 in Io.Threaded.initLockedStderr ()
> #19 0x00000000011653fc in Io.Threaded.lockStderr ()
> #20 0x00000000011263ff in Io.lockStderr ()
> #21 0x00000000011260c5 in debug.lockStderr ()
> #22 0x00000000011d25d5 in log.defaultLog__anon_35555 ()
> #23 0x00000000011d24cb in log.log__anon_35548 ()
> #24 0x00000000011d246b in log.scoped(.default).err__anon_32808 ()
> #25 0x00000000011ccb51 in start.main ()
> #26 0x00007ffff7cd21fe in ??? () at /usr/lib64/libc.so.6
> #27 0x00007ffff7cd231b in __libc_start_main () at /usr/lib64/libc.so.6
> #28 0x00000000012042b5 in _start ()
> 
> ```

Based on entry 16 my suspicion is that GLFW is modifying `environ`/`envp`. Zig std incorrectly assumes that `envp` is immutable and eagerly computes the length and converts it into a slice on startup, and `Io.Threaded.Environ.scan` will iterate over the full slice by length and not by scanning for the null sentinel, so if GLFW has used libc to modify the environment you end up with this crash. The hang is because `stderr` is already locked by the time the panic handler tries to lock it for writing.

This is a known bug: [https://codeberg.org/ziglang/zig/issues/35512](https://codeberg.org/ziglang/zig/issues/35512)

You might be able to work around it by adding

```zig
pub fn main(init: std.process.Init) !void {
    std.debug.lockStderr(&.{});
    std.debug.unlockStderr();
}

```

as your first two lines of your main function, before you initialize GLFW. This should trigger a scan for terminal color preference-related environment variables (e.g. `NO_COLOR`) and the result will subsequently be cached for the remainder of the application’s runtime, so that it won’t be affected if libc is used to mutate `environ`.

Whether or not `envp` (the C main argument) and `environ` alias or affect each other is an implementation detail of the libc, so that might explain why some OSes/distros have the problem and others don’t. (And Windows also handles environment variables very differently from POSIX.)

---

<div class="post-metadata">

**Author:** ![ThomasPais](https://ziggit.dev/user_avatar/ziggit.dev/thomaspais/32/9440_2.png) [@ThomasPais](https://ziggit.dev/u/ThomasPais)\
**Post date:** [July 12, 2026, 6:07am UTC](https://ziggit.dev/t/thread-panic-when-returning-an-error-after-initializing-glfw/16567/9 "2026-07-12T06:07:33Z")

</div>

Yes, that was it. Adding those two lines fixed the issue for me. Thanks for your help!
