How to Debug Terminal Emulation Issues in Ghostty Using Built-In Debug Features
Ghostty provides comprehensive debug logging through Zig's std.log infrastructure, which is enabled by default in debug builds and accessible via the GHOSTTY_LOG environment variable and platform-specific log streams.
Ghostty is a modern terminal emulator written in Zig that ships with extensive instrumentation for diagnosing terminal emulation bugs. The codebase leverages Zig's compile-time optimization levels and a configurable logging subsystem to expose the inner workings of its SIMD-optimized VT parser, stream handlers, and process execution layer. This guide covers how to enable, capture, and interpret Ghostty's debug output to isolate rendering issues, escape-sequence handling errors, and emulation mismatches according to the ghostty-org/ghostty source.
Debug Builds vs Release Builds
Ghostty's Zig build system defaults to the Debug optimization level, which compiles in all std.log.debug statements. When you run zig build without the -Doptimize flag, you automatically receive a binary with full debug instrumentation intact.
Release builds strip debug logs. Compiling with -Doptimize=ReleaseFast or -Doptimize=ReleaseSafe removes log.debug statements entirely, leaving only stderr-level logs. If you see no debug output despite setting environment variables, verify you are running a debug build by checking for the presence of debug symbols in your binary.
For deep terminal-emulation investigation, you can also toggle the debug constant in src/terminal/stream.zig to enable per-action tracing of the SIMD parser.
Logging Infrastructure and Environment Controls
Ghostty uses Zig's standard std.log API with platform-specific backends. The logging subsystem respects two primary controls: the build optimization level (which determines if debug code is compiled) and the GHOSTTY_LOG environment variable (which selects output destinations).
The GHOSTTY_LOG Environment Variable
Set GHOSTTY_LOG to control where logs are emitted:
# Enable all logging to default destinations
GHOSTTY_LOG=true ./zig-out/bin/ghostty
# Force stderr output specifically
GHOSTTY_LOG=stderr ./zig-out/bin/ghostty
# Enable both stderr and macOS unified logging
GHOSTTY_LOG=stderr,macos ./zig-out/bin/ghostty
On macOS, logs route to the Unified Logging System (viewable via the log CLI). On Linux, logs write directly to stderr, which journald captures when Ghostty runs as a systemd user service.
Key Debug Modules and Source Files
Ghostty's debug output is organized by module, with specific files handling different layers of the terminal emulation stack.
Core VT Parser Debugging (src/terminal/stream.zig)
The file src/terminal/stream.zig contains the SIMD-optimized VT parser. When the debug constant at the top of this file is set to true, it emits granular tracing for every parsed action:
// src/terminal/stream.zig
const debug = true; // flip to true for verbose stream debugging
Typical output includes:
debug: action: MoveCursor{row = 12, col = 5}
debug: execute: CSI(…)
debug: unimplemented OSC callback: 1337
Stream Handler Diagnostics (src/termio/stream_handler.zig)
The file src/termio/stream_handler.zig logs high-level protocol handling, including Kitty graphics mode changes, XTVERSION queries, and terminal working directory updates. Look for messages such as log.debug("pushing kitty keyboard mode …") and log.debug("terminal pwd: {s}", .{path}) around lines 300-320 of this file.
Process Execution Tracing (src/termio/Exec.zig)
The file src/termio/Exec.zig handles process spawning and execve flows. It logs command arguments via log.debug("starting command command={f}", .{ArgsFormatter{ .args = self.args }}), enabling you to verify exactly which shell or binary Ghostty is launching.
Additional relevant files include src/termio/Thread.zig for IO thread lifecycle events and src/termio/message.zig for message handling diagnostics.
Capturing Debug Output by Platform
macOS Unified Logging
On macOS, debug messages route to the system log. Capture them in real-time using:
sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'
This command displays all log.debug calls from Ghostty's subsystem, including VT parser actions and stream handler events.
Linux stderr and Journalctl
On Linux, run Ghostty with GHOSTTY_LOG=stderr and capture output directly:
# Run Ghostty with stderr logging enabled
GHOSTTY_LOG=stderr ./zig-out/bin/ghostty &
# Follow the stderr file descriptor
tail -f /proc/$(pgrep ghostty)/fd/2
If running as a systemd user service, use:
journalctl --user --unit app-com.mitchellh.ghostty.service -f
Enabling Verbose Stream Debugging
For investigations requiring visibility into every escape sequence, rebuild Ghostty after enabling the stream debug flag:
# Edit src/terminal/stream.zig and set const debug = true;
zig build -Doptimize=Debug
This produces verbose output showing exactly which VT sequences are processed, making it possible to isolate divergences between Ghostty's behavior and the expected terminal state.
Step-by-Step Debug Workflow
Follow this systematic approach to diagnose terminal emulation issues:
- Build a debug binary with
zig build(no optimization flags). - Set the environment variable
GHOSTTY_LOG=stderr(or appropriate destination for your platform). - Capture the log using the platform-specific command (macOS
log streamor Linuxjournalctl/tail). - Reproduce the issue by running the program or command that triggers the bug.
- Search the log for relevant messages using
grepor the log CLI's filtering. - Locate the source from the log tags—Zig's compile-time metadata includes file and line information.
- Escalate verbosity if needed by enabling the
debugflag insrc/terminal/stream.zig, rebuilding, and repeating steps 2-5.
Troubleshooting Common Pitfalls
| Symptom | Cause | Solution |
|---|---|---|
| No debug output on Linux | Binary compiled with -Doptimize=Release* |
Rebuild with default zig build (no optimize flag) |
| Logs appear after process exit | Buffering in stderr or missing real-time log stream |
Use stdbuf -oL on Linux or log stream on macOS |
| Excessive output interleaved with app data | debug flag in src/terminal/stream.zig set to true |
Reset debug = false after investigation |
Summary
- Ghostty debug builds are default: Running
zig buildwithout optimization flags includes alllog.debugstatements. - Control output with
GHOSTTY_LOG: Set tostderr,macos, ortrueto enable logging to specific destinations. - Key files for emulation debugging:
src/terminal/stream.zig(VT parser),src/termio/stream_handler.zig(protocol handling), andsrc/termio/Exec.zig(process spawning). - Platform-specific capture: Use
log streamon macOS andjournalctlorstderrredirection on Linux. - Granular tracing available: Toggle the
debugconstant insrc/terminal/stream.zigto see every parsed VT action.
Frequently Asked Questions
How do I know if Ghostty is running a debug build?
Check your build command history. If you ran zig build without the -Doptimize flag, you have a debug build. Debug builds include std.log.debug statements, while release builds (-Doptimize=ReleaseFast or -Doptimize=ReleaseSafe) strip these calls entirely. You can verify by checking if GHOSTTY_LOG=stderr ./ghostty produces debug messages.
Why am I seeing no debug output on macOS even with GHOSTTY_LOG set?
On macOS, debug logs route to the Unified Logging System by default, which filters messages below the fault level when viewed in Console.app. You must use the command-line tool with the debug level predicate: sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'. Alternatively, force stderr output with GHOSTTY_LOG=stderr.
What is the difference between src/terminal/stream.zig and src/termio/stream_handler.zig?
src/terminal/stream.zig implements the low-level VT parser that tokenizes escape sequences into actions. src/termio/stream_handler.zig implements the handlers that execute those actions and manages higher-level terminal state like Kitty graphics modes and working directory tracking. For parser bugs, check stream.zig; for behavioral bugs, check stream_handler.zig.
Can I add my own debug logging to Ghostty when developing patches?
Yes. Import const std = @import("std"); and use std.log.debug("message: {s}", .{value}); in your code. These statements appear automatically in debug builds alongside existing Ghostty diagnostics, provided you maintain the debug optimization level during compilation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →