Integrate with common shells in order to track cwd… - #41
Open
savetheclocktower wants to merge 3 commits into
Open
Integrate with common shells in order to track cwd…#41savetheclocktower wants to merge 3 commits into
cwd…#41savetheclocktower wants to merge 3 commits into
Conversation
5 tasks
savetheclocktower
force-pushed
the
shell-integration-for-cwd
branch
from
August 21, 2026 06:21
86b293e to
3a5040d
Compare
savetheclocktower
force-pushed
the
shell-integration-for-cwd
branch
from
August 21, 2026 06:33
3a5040d to
e9fd34c
Compare
…for bash, zsh, fish, and PowerShell. Implements OSC 633, opening the door for further enhancements; but the main value is in tracking `cwd` so we can update the pane item's path and lay the groundwork for proper interpretation of relative paths.
savetheclocktower
force-pushed
the
shell-integration-for-cwd
branch
from
August 21, 2026 06:56
e9fd34c to
1335163
Compare
# Conflicts: # lib/.build-hash # spec/element-spec.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…and support some other future features.
FULL DISCLOSURE: I knew this was the anti-sweet spot of
Also: in some senses VS Code's source is rather straightforward to read; in others, it's utterly infuriating. If you're consulting their implementation of something as you implement a feature, you'll go off on labyrinthine tangents as you chase types and utility functions through a web of interlocked files.
So this was the perfect feature to hand to Claude Code with some very explicit guidance. I had copied over the shell integration files and done some imperfect variable renaming. I had also started to adapt the method that performed injection for each type of shell and had gotten tired of all the tendrils, so that's where I handed it off to Claude.
VS Code came up with their own terminal escape sequences so that a host could send metadata to a terminal about the session. One of the pieces of metadata that can be sent over OSC 633 is the current working directory. That's the main one we care about for this feature, so that's all that's been implemented.
Deep-dive on OSC 633 and terminals
The architecture is pretty clever! OSC 633 is about communicating metadata about a terminal session in an in-band way. There's no extra band for metadata, so it's got to hide itself in the signal. This is very similar to how phones worked in the analog days; instead of having a separate channel for controlling the phone, all control had to happen over the same band as the actual voice communication. Hence first rotary phones (numbers of clicks) and then touch-tone phones (specific pairs of pitches that could be detected by equipment).
This is not a new idea for host/terminal communication; it's just arguably more abstract than most of its analogs. ANSI escape sequences are already used to signal mode changes (like changing text color or weight) or for marking hyperlinks (the OSC 8 standard we've implemented in another PR). Here it's more like dipping out of the stream of text to deliver commentary, as an author or translator might do in prose with brackets.
But because it's communicating genuine metadata and not just superficial things like style, security has to be considered. Because the communication is in-band, there is no obvious way to distinguish between a shell sending metadata… and a nefarious output stream trying to impersonate that shell. Simply
catting a file could emit the escape sequences that would open and close that metadata channel. Just as it was with analog phones — tones emitted by phones could be impersonated by 2600hz blue-boxes if you felt like calling someone long-distance but didn’t feel like paying for it.And some of that data is sensitive, or could be used to make malicious code seem more benign (e.g., by reporting that it's running a simple, innocuous command when it's actually deleting files or reading sensitive data).
The way around this is with a nonce — a one-time secret known to both sides of the communication. This is where the phone analogy falls down, but imagine:
The solution is to have Alice and Bob agree ahead of time on some sort of shared secret, like a code word. Whenever Bob receives a message and is unsure who sent it, he knows that if the code word is present, it’s from Alice. If it’s not present, it’s not to be trusted. This solution is only possible because Mallory can’t hear what Alice says to Bob, or else she’d be able to overhear the code word.
The program sends its data to the host, but it cannot overhear whatever extra data the host sends to the terminal. So important pieces of metadata are marked with a nonce as a way of proving their authenticity. Not everything in OSC 633 carries a nonce; but if it doesn’t, you can assume it’s not sensitive enough to warrant it.
There are other things that we might care about picking up from OSC 633. The most salient one is the ability to spy on environment variables and be notified when they change. This is a big deal for tools like
virtualenvand can magnify signals like “which version of Python is running right now” or “which AWS profile is active.”Another is being able to mark boundaries of the output semantically. If you know where the prompt starts and ends, then you can exclude it from drag-selection of text and make it easier to copy actual output. (Same with
zsh’sRPROMPT, whose text often finds its way into Claude Code sessions at work because I copy terminal output straght from iTerm 2. Presumably if I copied from VS Code, it would ignore theRPROMPT! Yeah, this is a feature worth emulating eventually.)But those can wait! The goal is to keep this PR comprehensible and ship a feature that is useful on its own but also unblocks other features (like path detection).
Testing the machinery
The penance for offloading a feature to a robot is to be thorough about tests. There are three parts that make this work:
terminalextension to introduce the two parts to one another — recognizing when the user’s shell is an injectable shell and ensuring that that shell is started up in such a way that its own “middleware” can be injected (but otherwise be seamless to the user).The second and third parts have thorough test coverage, though each one in isolation. (The XTerm addon is easy to test; you can write data to a terminal and then test that the data is read as expected by the addon.) The scripts themselves — written, as they are, in various shell script dialects — don’t have any test coverage, nor do any of the tests attempt to establish a genuine terminal session with a PTY. Which is fine; no way to set that up such that the specs can be run fron anyone’s machine!
One way around this might be to introduce some Playwright (end-to-end) tests eventually. We use Playwright for our post-build smoke tests; in this case we could write four Playwright test suites (one for each integration) and run it on GitHub Actions in an appropriate runner for the integration being tested.
But this can be a future thing! The feature is easy enough to get working and is a good foundation for what is to come, and the impact of it possibly not working correctly is not far from the status quo. For now, this means that your
cwdwill be updated as it changes; therefore:cwdit used, rather than the one it had when you opened it.cwd, not the initialcwd.tree-view:reveal-active-file, the active folder in the project should be highlighted in the tree view (ifcwdis within the project).Please test this manually! Especially if you use a non-standard shell like
fish, or if you are a Windows user. I know it works for me withzsh, but I haven’t tested the other integrations yet.