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:

  1. Build a debug binary with zig build (no optimization flags).
  2. Set the environment variable GHOSTTY_LOG=stderr (or appropriate destination for your platform).
  3. Capture the log using the platform-specific command (macOS log stream or Linux journalctl/tail).
  4. Reproduce the issue by running the program or command that triggers the bug.
  5. Search the log for relevant messages using grep or the log CLI's filtering.
  6. Locate the source from the log tags—Zig's compile-time metadata includes file and line information.
  7. Escalate verbosity if needed by enabling the debug flag in src/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 build without optimization flags includes all log.debug statements.
  • Control output with GHOSTTY_LOG: Set to stderr, macos, or true to enable logging to specific destinations.
  • Key files for emulation debugging: src/terminal/stream.zig (VT parser), src/termio/stream_handler.zig (protocol handling), and src/termio/Exec.zig (process spawning).
  • Platform-specific capture: Use log stream on macOS and journalctl or stderr redirection on Linux.
  • Granular tracing available: Toggle the debug constant in src/terminal/stream.zig to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →