Skip to content

Shell Integration

Otty injects small shell hooks that emit OSC 133 (FTCS) prompt marks. These power command outlines, working-directory tracking, and exit-status indicators.

📸 TL;DR

Our recommendation is to keep this default value: enabled.

Supported shells

  • zsh
  • bash
  • fish

Auto-detected from $SHELL. Other shells work as terminals but don't get the integration features.

What's emitted

SequenceWhenWhy
OSC 133 ; ARight before drawing the promptMarks prompt boundaries
OSC 133 ; BAfter the prompt, before inputDistinguishes prompt from typed input
OSC 133 ; CWhen the command starts runningOutput begins here
OSC 133 ; D ; <exit>When the command finishesRecords exit status
OSC 7 ; file://<host><cwd>At every promptTracks pane cwd

Otty consumes these to build per-pane outlines and gutter indicators. See Outline.

Features that rely on it

Otty is a perfectly good terminal without shell integration — but the features below are driven by the marks above, so they go dark (or fall back to a guess) when it's off. Each one is flagged in Settings: turning it on while integration is disabled still saves the setting, but pops a warning that it won't do anything until you switch integration back on.

Command status — OSC 133 ; C / D ; <exit>

  • Outline & command navigation — the per-pane command list and jump-to-prompt. See Outline.
  • Exit-status gutter dots — the green / red status marker beside each command.
  • Tab badgesWhen Command Finishes and When Command Fails dots on the tab.
  • Command notifications & soundsNotify on Command Finish, Notify on Error Exit, Beep on Error Exit.
  • On-device autocomplete learning — only commands that exit 0 are learned.
  • Re-run processes on session restore — restoring what was running needs the command-start mark.

Working directory — OSC 7

  • Inherit working directory for new tabs, splits, and windows.
  • Auto-record visited folders — the database behind Frequent Folders (otty jump and Open Quickly's Folders tab).

Prompt boundary — OSC 133 ; B

  • Autocomplete — inline ghost text and the candidate panel locate the prompt from the input mark. Without it they fall back to a ~1s timer and lose accuracy.

Injected wrappers

  • edit / view / jump / learn shell functions (the Omit otty prefix option) and any custom aliases.
  • SSH integration — the ssh wrapper that forwards env, installs terminfo, and enables remote file/git access.

Everything else — terminal bell, app notifications (OSC 9 / 777), and code-agent badges (delivered over IPC) — works regardless.

How it loads (and how to audit it)

The integration scripts ship as readable, code-signed files inside the app bundle, so you can read exactly what Otty runs in your shell before trusting it:

Otty.app/Contents/Resources/shell-integration/
├── otty-integration.zsh                       # the zsh payload (OSC 133, ssh wrapper, …)
├── otty-integration.bash
├── otty-integration.fish
├── zsh/.zshenv                                # zsh loader (ZDOTDIR entry)
└── fish/vendor_conf.d/otty-shell-integration.fish   # fish loader

Nothing is synthesized at runtime or written to a hidden temp file — these are the actual scripts, signed and notarized with the app.

How they reach your shell depends on the shell, and is controlled entirely by the Settings → Shell → Shell Integration toggle:

  • zsh — Otty points ZDOTDIR at its bundled zsh/ dir for the session it launches. That .zshenv immediately restores your real ZDOTDIR, runs your own .zshenv, then loads Otty's payload on the first prompt (so its marks win over plugin managers). Your ~/.zshrc and other dotfiles are not edited.
  • fish — Otty prepends its bundled dir to XDG_DATA_DIRS, so fish auto-loads the vendor_conf.d entry, which then removes that dir again so child processes don't inherit it. Your config.fish is not edited.
  • bash — bash has no clean per-spawn auto-load, so Otty adds a small, clearly-marked block to ~/.bashrc (and a ~/.bash_profile shim) that sources the bundled payload only when launched by Otty. The block is inert in other terminals and removed when you turn the toggle off.

tmux is the one exception for zsh/fish: a tmux server captures its environment once at start, so the ZDOTDIR / XDG_DATA_DIRS injection can't reach panes it later spawns. When tmux is installed, Otty additionally adds the same guarded managed block to your zsh/fish rc so tmux panes still get the integration. Turning the toggle off removes every block.

Opting out

Everywhere — Settings (no config file needed). Open Settings → Shell → Shell Integration and turn off Provide Shell Integration. Otty pops a Turn off shell integration? confirmation, then removes the line it added to your shell startup files (the bash block and any tmux managed blocks) and stops the per-session ZDOTDIR / XDG_DATA_DIRS injection for zsh and fish. Prompt marks, command status, CWD tracking, the edit/view/jump wrappers, custom aliases, and SSH integration go dark in every Otty shell. Toggle it back on anytime — the same dialog re-installs everything. This is the same shell-integration switch you'd set in your config file, so the two stay in sync.

Per-shell — in your rc file. Leave integration on globally but skip it for one shell by exporting, before Otty's payload loads:

bash
export OTTY_DISABLE_INTEGRATION=1

Per-window — by launching with the flag:

bash
otty open --no-integration

Verifying

In an integrated shell:

bash
echo "marks: $OTTY_INTEGRATION"
# marks: 1

Or watch the Outline gutter — it only renders when marks arrive.

See also

Otty