Skip to content

Instantly share code, notes, and snippets.

@telnet2
Created July 19, 2026 04:07
Show Gist options
  • Select an option

  • Save telnet2/35c63a873e3e8259a0bd96af13ab6ecb to your computer and use it in GitHub Desktop.

Select an option

Save telnet2/35c63a873e3e8259a0bd96af13ab6ecb to your computer and use it in GitHub Desktop.
Ghostty: fix new panes/tabs opening at / (incl. the Claude Code TUI case) — zsh OSC 7 hook

Ghostty: fix new panes/tabs opening at / (incl. the Claude Code case)

New Ghostty splits/tabs/windows should open in the current working directory. On some setups they open at / instead. This documents the root cause and a robust zsh fix, including the subtle case where the pane is running a long-lived TUI such as Claude Code.

Environment where this was diagnosed

  • Host: ASUS Ascent GX10 (NVIDIA DGX Spark / GB10), aarch64
  • OS: Ubuntu 24.04, zsh + Prezto
  • Terminal: Ghostty 1.3.1, launched as a systemd/GTK single-instance app (ghostty --gtk-single-instance=true), spawning the shell via sh -c /usr/bin/zsh

How Ghostty inherits the directory

Ghostty (window-inherit-working-directory = true, the default) tracks each surface's working directory from the OSC 7 escape sequence the shell emits:

ESC ] 7 ; kitty-shell-cwd://<host><path> BEL

If the shell never emits OSC 7, Ghostty has nothing to inherit and falls back to the directory the Ghostty process was started in — which for a systemd/desktop-launched instance is /. Hence new panes open at /.

Why the built-in shell integration didn't fire

Ghostty ships zsh integration that emits OSC 7 automatically, injected by setting ZDOTDIR to its integration dir. That injection is bypassed here because the shell is started via sh -c /usr/bin/zsh (not launched directly by Ghostty), and its .zshenv guard ([[ -o interactive ]] + a ${(%):-%x} self-path trick) doesn't resolve in this setup. Net effect: the OSC 7 hook is never registered, so cwd is never reported.

The fix — emit OSC 7 ourselves

Append to the end of ~/.zshrc (after Prezto/your prompt framework loads, so nothing clobbers the hook):

# Report cwd to the terminal via OSC 7 so new Ghostty panes/tabs inherit the
# current directory. (Ghostty's own integration doesn't register on this host.)
autoload -Uz add-zsh-hook
_ghostty_osc7_cwd() { printf '\033]7;kitty-shell-cwd://%s%s\007' "${HOST}" "${PWD}" }
add-zsh-hook -Uz precmd  _ghostty_osc7_cwd   # every prompt
add-zsh-hook -Uz chpwd   _ghostty_osc7_cwd   # every cd
# Also report right before a command runs, so a long-lived foreground app
# (e.g. `claude`) that blocks the shell still leaves Ghostty an up-to-date cwd
# for panes/tabs opened while it runs.
add-zsh-hook -Uz preexec _ghostty_osc7_cwd

Open a new pane to pick it up (existing shells: source ~/.zshrc). It's harmless in other terminals — they ignore the unknown OSC 7.

Why the preexec line matters (the Claude Code case)

Ordinary commands (ls) finish instantly and drop you back at a prompt, so precmd re-reports the directory constantly; whenever you split, you're standing on a fresh report.

A full-screen TUI like Claude Code never returns to a prompt — it holds the terminal for the whole session, so the shell is blocked and sends no further OSC 7. New panes opened during it can only inherit the last report made before it launched. preexec guarantees that last report is emitted at the exact handoff instant, right before the app takes over.

What Claude Code actually emits (verified by capturing its startup in a pty)

  • OSC 7 (report cwd): never — so it does not overwrite Ghostty's stored directory
  • No terminal reset (no ESC c RIS, no ESC[!p soft reset) — so it does not erase it
  • It does: enter the alternate screen (?1049h), synchronized output (?2026h/l), mouse tracking, bracketed paste, focus reporting, Kitty keyboard protocol, and emits OSC 0 (title), OSC 8 (hyperlinks), OSC 9 (notification)

Conclusion: Claude Code is a well-behaved TUI that reports no directory and resets nothing. The / fallback is purely the absence of a fresh cwd report while it holds the terminal — which the preexec hook closes.

Scope / limitations

  • Split / tab / new-window from within a pane → inherits that pane's cwd. ✅ Fixed.
  • A brand-new window from the desktop icon / taskbar → doesn't inherit from any surface; it uses the daemon's own cwd (/). To make those start at home instead, set a fixed default in Ghostty config: working-directory = ~.

TL;DR

Emit OSC 7 from zsh on precmd + chpwd + preexec. The first two fix ordinary panes; preexec fixes panes opened while a long-lived TUI (Claude Code) holds the terminal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment