Every dual-pane file manager with an embedded terminal has the same small, constant friction: you navigate to a
folder in the panel, then type cd to the same folder in the shell — or the other way round. cwdio removes it
by keeping the two in step in both directions. This is the story of how, retold from ADR 0016 and its
directory-sync addendum.
The problem as felt
An embedded terminal that opens in the panel’s folder is table stakes. The friction starts a minute later:
you cd into packages/vfs, run a build, then go looking for the output in the panel — which is still
three levels up. Or you drill into a folder in the panel, switch to the terminal, and the prompt is still
where you left it. Two working directories, and you are the synchronisation mechanism.
Fixing one direction is easy. Fixing both without the two chasing each other is the actual problem.
The signal: OSC 7
A shell has no way of telling a terminal where it is — unless it prints something. Modern terminals agreed on
one thing to print: OSC 7, an operating-system-command escape sequence carrying the working directory as a
file:// URL:
ESC ] 7 ; file://hostname/C:/projects/cwdio/packages/vfs BEL
Windows Terminal, iTerm2, WezTerm and VS Code all understand it, mostly to name tabs and to open a new tab in the same folder. cwdio uses it as the source of truth for the terminal’s cwd.
The shell has to be taught to emit it. cwdio injects a per-shell snippet at spawn (shell-integration.ts in
the terminal package): PowerShell 7 and Windows PowerShell get a wrapped prompt function via an encoded
command, so the user’s own prompt is preserved; cmd gets a PROMPT that includes the directory; bash gets a
PROMPT_COMMAND. Every prompt then announces the cwd. Shells cwdio does not know how to instrument — a bare
zsh, sh — are left alone. That is deliberate graceful degradation: a missing OSC means the sync simply
does not fire, and a snippet can never break a terminal it was not written for.
The idle rule
The dangerous direction is panel → terminal, because it means writing into the shell’s stdin. Two things had to be true before cwdio would do that.
First, the terminal must be idle. Injecting cd D:\other into a shell that is halfway through running a
build would be a real bug, not an inconvenience. Rather than adopt the fragile OSC 133 prompt-marking
protocol, cwdio derives idleness from something it already has: an OSC 7 has arrived since the last line the
user submitted. If a prompt has been printed after your last command, the shell is waiting for input.
Second, the two directions must not chase each other. A cd in the terminal moves the panel; the panel
moving would normally cd the terminal; which prints a prompt; which reports a cwd. cwdio breaks the loop with
idempotent path comparison and a per-session syncedCwd — if the terminal is already where the panel is
going, nothing is sent. Boring, and exactly what you want.
The other direction has its own guard: a cd reported by the shell only moves the panel when that
terminal has keyboard focus. A background script that wanders through directories does not yank your
panels; the cd you type in the terminal you are looking at does.
What shipped
- Navigating the active panel
cds the idle terminal bound to that pane; the tab picks up the folder’s name. - A
cdtyped in a focused terminal moves the active panel. - Each session tracks its own cwd, idle state and last-synced path — the first cut kept one global “last shell cwd”, which was wrong the moment there were two sessions.
- View ▸ Sync terminal to folder turns the whole thing off. It is on by default.
- Early input is buffered until the pty exists, so a
cdsent while the shell is still spawning is not dropped.
Over SSH
When an SFTP panel is open, the terminal for that pane is a real SSH shell on the same authenticated
connection — no second login. The same idle model applies, with one more rule: sync is locality-aware.
A local pty is only ever cd’d by a local panel; a shell on server A only by a panel on server A. A Windows
path never lands in a POSIX shell, and a cd in one server’s shell never moves a panel browsing another. A
remote shell with shell integration syncs both ways; one without follows the first navigation and then stops
— safe, with no stdin injection into a shell whose state cwdio cannot see.
What it deliberately does not do
- It never writes into a busy terminal. If that means the panel and the prompt disagree for the length of a long build, that is the correct outcome; they reconcile at the next prompt.
- It does not use OSC 133 or any protocol the shell has to opt into beyond the snippet cwdio injects.
- It does not detach terminals into separate OS windows. They are dock panels — movable beside, below or on top of the file panels — but they stay in the window.
The feature was later proven against the real Electron shell with real node-pty sessions: forward sync,
reverse sync and a chosen non-default shell all round-tripped in the end-to-end suite. The one thing not
covered automatically is each specific shell’s integration snippet beyond PowerShell — cmd, WSL and Git
Bash are exercised by hand from the profile picker.
If you want the user-facing version, it is on the terminal sync feature page; the keys are in the shortcut table.