Every SSH tab has a Files sidebar that follows the directory your shell
is in, so cd in the terminal moves the file browser with you. It
follows one of two ways, and only one of them is exact. This page
explains the difference and how to get the exact one.
Everything here is optional and every step is yours to take: Oryxis never edits files on your servers.
| What you want | Works already | What to do |
|---|---|---|
| The sidebar roughly tracks your directory | Yes, if your prompt puts the path in the window title | Nothing |
| Exact tracking, any prompt, paths with spaces or accents | No | Install the OSC 7 snippet |
| Same, inside tmux | No | The snippet, plus passthrough |
When the shell says nothing about where it is, Oryxis falls back to
reading the window title. That works because the default bash and
zsh prompts on most distributions set the title to user@host: ~/path,
so the path is sitting right there.
It is a heuristic, and it fails the moment your prompt stops matching that shape:
- A prompt framework (starship, powerlevel10k, oh-my-zsh themes) that writes its own title, or none at all.
- A title that abbreviates:
~instead of your real home path, or a truncated.../deep/path. - A directory whose name contains
:or looks like a hostname. - Anything that sets the title itself while running (
sshfrom inside the session,vim, a build tool).
None of that is fixable by parsing harder. The shell is the only thing that actually knows its directory, so the answer is to have it say so.
OSC 7 is the standard escape sequence a shell uses to report its
working directory:
ESC ] 7 ; file://<host>/<percent-encoded path> BEL
It originated in macOS Terminal and is understood today by iTerm2,
kitty, WezTerm, VS Code, GNOME Terminal and everything else built on
VTE. Oryxis accepts both terminators (BEL and ESC \), accepts a
missing host (file:///path), and percent-decodes the path.
Installing it is worth doing for reasons beyond Oryxis: the same snippet makes every one of those terminals open new tabs in the right directory.
Save this as ~/.config/oryxis/osc7.sh (any path works):
# Report the shell's working directory (OSC 7) so the terminal's file
# browser follows it exactly. bash and zsh. Sourcing it twice is a
# no-op.
if [ -z "${__oryxis_osc7:-}" ]; then
__oryxis_osc7=1
# Percent-encode the path BYTE by byte, under LC_ALL=C. This is the
# part that is easy to get wrong: a raw space would end the URL
# early, and encoding per CHARACTER instead of per byte turns every
# accented directory name into mojibake on the other side.
__oryxis_urlencode() (
LC_ALL=C
str=$1
while [ -n "$str" ]; do
safe=${str%%[!a-zA-Z0-9/:_\.\-\!\'\(\)~]*}
printf '%s' "$safe"
str=${str#"$safe"}
if [ -n "$str" ]; then
printf '%%%02X' "'$str"
str=${str#?}
fi
done
)
__oryxis_cwd() {
printf '\033]7;file://%s%s\007' \
"${HOSTNAME:-$(uname -n)}" "$(__oryxis_urlencode "$PWD")"
}
# Registered through each shell's own pre-prompt hook. bash has no
# `precmd`, zsh has no `PROMPT_COMMAND`, so each branch uses the one
# its shell actually runs.
if [ -n "${ZSH_VERSION:-}" ]; then
autoload -Uz add-zsh-hook 2>/dev/null && add-zsh-hook precmd __oryxis_cwd
elif [ -n "${BASH_VERSION:-}" ]; then
PROMPT_COMMAND="__oryxis_cwd${PROMPT_COMMAND:+;$PROMPT_COMMAND}"
fi
# Report once at load, so the first prompt is already correct.
__oryxis_cwd
fiThe PROMPT_COMMAND line prepends, keeping whatever your prompt
already set, so a framework that uses it keeps working.
Add this to the end of ~/.bashrc or ~/.zshrc:
[ -f ~/.config/oryxis/osc7.sh ] && . ~/.config/oryxis/osc7.shbash users, read this one: a login bash reads ~/.bash_profile
(or ~/.bash_login, or ~/.profile) instead of ~/.bashrc. Most
distributions ship a ~/.bash_profile that sources ~/.bashrc for
you; if yours does not, add the usual line to it:
[ -f ~/.bashrc ] && . ~/.bashrczsh has no such split: ~/.zshrc is read by every interactive shell,
login or not.
Open a new shell (or source your rc), open the Files tab in the
terminal sidebar, and cd somewhere with a space in the name. The
sidebar should land exactly there. If it follows your cd but lands on
the wrong path for names with spaces or accents, the snippet is loaded
but the encoding half is not; re-copy the __oryxis_urlencode function.
tmux drops unknown escape sequences unless passthrough is on. On
tmux 3.3 or newer, add to ~/.tmux.conf:
set -g allow-passthrough onthen reload (tmux source-file ~/.tmux.conf) or apply it to the
running server with tmux set -g allow-passthrough on.
The option was added in tmux 3.3. On older versions the line is a
parse error at startup, and there is no way to get the sequence out of
tmux there. Check with tmux -V first: Ubuntu 22.04 and
AlmaLinux/RHEL 9 ship 3.2a, so this affects a lot of otherwise current
servers.
If you also want your typed commands to show up in the History tab inside tmux, that needs its own snippet, and it is covered in the tmux guide.
Up to v0.12 there was a Settings toggle, "Force exact directory following (OSC 7)", that typed a setup line into your shell on connect and tried to erase its own echo afterwards. It is gone.
It was removed because the technique cannot be made safe. The app
writes those bytes the moment the SSH session is established, but it
has no way to know what the shell is doing at that instant: on a host
with a long MOTD or a slow /etc/profile.d, the bytes land before the
shell reaches its first prompt, the terminal echoes them raw, and the
self-erasing trailer wipes the wrong region because the cursor
position it was calibrated against never existed. What you get is the
setup block sitting on your screen for the rest of the session, which
is the opposite of the point.
No other terminal does this, which in hindsight was the tell. Every
client that installs shell integration on a remote host replaces the
command the SSH channel runs rather than typing into the running
shell: kitty's ssh kitten ships a bootstrap script and execs your
login shell after it, VS Code injects through --init-file / ZDOTDIR
for the shells it launches itself and
documents that this does not work over plain SSH,
and WezTerm and iTerm2 hand you a snippet exactly like the one above.
That leaves your dotfiles as the right place for this, which is where this page puts it.
Nothing to clean up on your servers: the old toggle never wrote to a
file, it only set PROMPT_COMMAND inside the running shell, so the
emitter died with that shell on disconnect. The one exception is a
shell that outlives the connection, inside tmux or screen: there it
keeps emitting until that shell exits, and unset -f __oryxis_o7 plus
removing __oryxis_o7; from the front of PROMPT_COMMAND clears it
early if you want it gone before then.