TUI aborts with SIGABRT (double panic) when stderr is already closed during terminal restore
Two SIGABRT crash reports showed up in ~/Library/Logs/DiagnosticReports after codex sessions ended abruptly (in one case the parent process had already exited; in the other, a node parent's pipe was closed). Both crashes have the same shape on two different builds, so this looks like shared teardown code rather than a packaging problem.
What the stacks say happens:
- The TUI tears down and
codex_tui::TerminalRestoreGuard::restore_silentlyruns. - It writes to stderr, but stderr is already gone, so std's
_eprintpanics with the usual "failed printing to stderr". - The panic hook then tries to print that panic message to the same dead stderr, panics again, and the double panic becomes
abort().
Crashed thread from the npm build (@openai/codex 0.143.0, aarch64-apple-darwin, image UUID fd137d40-9f13-3914-8d0f-4e79bdaf657d):
0 libsystem_kernel.dylib __pthread_kill +8
1 libsystem_pthread.dylib pthread_kill +296
2 libsystem_c.dylib abort +148
3 codex +0xb077f00
4 codex std::process::abort +12
5 codex std::panicking::panic_with_hook +796
6 codex +0x9099fc8
7 codex std::sys::backtrace::__rust_end_short_backtrace +12
8 codex rust_begin_unwind +32
9 codex core::panicking::panic_fmt +40
10 codex std::io::stdio::_eprint +164
11 codex +0x84428f4
12 codex +0x1146498
13 codex std::panicking::panic_with_hook +616
14 codex +0x9099fc8
15 codex std::sys::backtrace::__rust_end_short_backtrace +12
16 codex rust_begin_unwind +32
17 codex core::panicking::panic_fmt +40
18 codex std::io::stdio::_eprint +164
19 codex codex_tui::TerminalRestoreGuard::restore_silently +76
20+ (tokio worker frames below)
I have an identical stack from the Codex.app bundled CLI (26.623.141536, image UUID 83e2a45e-1718-37c4-8f97-fb511c389065); only the unsymbolicated offsets differ. Frame 19 is TerminalRestoreGuard::restore_silently in both.
Impact is mostly cosmetic since the process was exiting anyway, but the abort skips whatever cleanup was left. One side effect I can actually measure: sockets under /tmp/codex-browser-use never get unlinked on abrupt teardown, and I accumulated 30+ dead ones within a few days.
I don't have a minimal repro. Closing the terminal tab (or the parent dying) while a TUI session is alive seems to be enough. A function named restore_silently presumably shouldn't be able to panic at all, so ignoring write errors there (and maybe in the panic hook's stderr path) would cover it regardless of how stderr died.
Possibly related: #10248 is the same failure class on stdout during --json streaming.
Environment: macOS 26.5.1 (arm64), codex-cli 0.143.0 (npm) and Codex.app 26.623.141536.