Skip to content

Wrap shell-integration OSCs in tmux DCS-passthrough#197

Draft
dakra wants to merge 1 commit into
mainfrom
tmux-osc-passthrough
Draft

Wrap shell-integration OSCs in tmux DCS-passthrough#197
dakra wants to merge 1 commit into
mainfrom
tmux-osc-passthrough

Conversation

@dakra
Copy link
Copy Markdown
Owner

@dakra dakra commented Apr 27, 2026

Summary

  • When $TMUX is set, the bash/zsh/fish shell integration wraps every OSC sequence (OSC 7 cwd, OSC 133 prompt markers, OSC 51 ghostel_cmd) in tmux's DCS-passthrough envelope (\ePtmux;<body-with-doubled-ESCs>\e\\). tmux unwraps one level and forwards the body to ghostel, restoring directory tracking and prompt navigation inside plain tmux sessions.
  • Single __ghostel_emit_osc helper per shell; the five existing emitters route through it. Byte-output identical across bash/zsh/fish.
  • Opt-in on the user side: requires set -g allow-passthrough on in tmux.conf (tmux 3.3+). Without it, tmux drops the wrapped sequence — same as before, no regression.
  • README gains an "Inside tmux" subsection covering both prerequisites: enabling passthrough and sourcing the integration manually from ~/.bashrc / ~/.zshrc (auto-injection's env vars don't survive tmux pane spawns).

Refs #195. Independent of the in-flight feat/tmux (control-mode) work — that branch handles tmux -CC; this fix covers users on plain tmux.

Notes from implementation

  • Fish needs \\\\ (not \\) in the single-quoted printf format because fish's single-quote consumes one level of \\\ before printf sees it.
  • The new test had to work around bash's DEBUG trap firing before commands inside the script body during sourcing — it emits a sentinel and slices output after it.

Test plan

  • make -j4 all green (39 + 96 + 190 tests, lint clean)
  • Byte-checked all three shells via od -c — wrapped form matches expected \ePtmux;\e\e]…\e\e\\\e\\ with $TMUX set, bare OSC otherwise
  • New ghostel-test-shell-osc-tmux-wrapping (elisp suite) spawns each shell, asserts byte-exact output for both $TMUX states
  • Live verification inside a real tmux pane (with allow-passthrough on): cd should update default-directory; find-file should default to the shell's cwd

When a user runs ghostel's shell integration inside tmux (without
control-mode integration), tmux strips OSC sequences before they reach
ghostel.  The shell emits OSC 7, but `default-directory' never updates,
so `find-file' and friends don't follow the shell's cwd.  Reported in
issue #195.

Fix: when `$TMUX' is set, the shell integration wraps every OSC in
tmux's DCS-passthrough envelope (`\ePtmux;<body-with-doubled-ESCs>\e\\').
tmux unwraps one level and forwards the bare OSC to ghostel.  Users
opt in by adding `set -g allow-passthrough on' to `~/.tmux.conf'
(tmux 3.3+); without that, tmux drops the wrapped sequence — same
behavior as before, no regression.

A single `__ghostel_emit_osc' helper in each of the bash/zsh/fish
integration files takes the OSC body and emits either the bare
`\e]<body>\e\\' or the wrapped form, depending on `$TMUX'.  The five
existing emitters (`__ghostel_osc7', `__ghostel_prompt_start',
`__ghostel_prompt_end', `__ghostel_preexec', `ghostel_cmd') route
through it.  Byte output is identical across all three shells; fish's
helper needs extra escaping because fish's single-quote consumes one
level of `\\' → `\' before printf sees it.

Tests: new `ghostel-test-shell-osc-tmux-wrapping' (elisp suite) spawns
each shell, sources the integration with and without `$TMUX', and
asserts byte-exact output.  README gains an "Inside tmux" subsection
documenting the `allow-passthrough' requirement and the
`INSIDE_EMACS'-propagation caveat.

Refs #195
@dakra dakra added the claude-review Trigger a review from Claude label May 3, 2026
@dakra dakra removed the claude-review Trigger a review from Claude label May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant