# How to Debug Terminal Emulation Issues in Ghostty Using Built-In Debug Features

> Debug Ghostty terminal emulation problems effectively using built-in debug features. Learn how to leverage Ghostty's logging and environment variables for faster troubleshooting.

- Repository: [Ghostty/ghostty](https://github.com/ghostty-org/ghostty)
- Tags: how-to-guide
- Published: 2026-05-01

---

**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:

```bash

# 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:

```zig
// 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:

```bash
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:

```bash

# 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:

```bash
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:

```bash

# 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.