Open Current Directory in <IDE>: follow the foreground process into git worktrees
Problem
"Open Current Directory in VS Code" (and every other TerminalDirectoryOpenTarget — Cursor, Zed, Finder, inline VS Code, …) opens the main repo root instead of the git worktree the user is working in when they're running claude --worktree.
Root cause
claude --worktree creates a linked worktree (e.g. .claude/worktrees/<name>) and runs the agent process with its cwd set to the worktree, but leaves the interactive shell in the repository root. cmux only learned a terminal's directory from the shell's OSC 7 report, and focusedTerminalDirectoryURL() opened that shell cwd verbatim — it never consulted the foreground job.
Confirmed on a real session:
| Process | cwd |
|---|---|
claude --session-id … (foreground agent) | …/cmux/.claude/worktrees/twinkly-kindling-bubble ← worktree |
parent zsh (the tracked shell) | /Users/…/cmux ← repo root |
Fix
Prefer the focused pane's foreground-job cwd over the shell's reported cwd, reusing Workspace.liveForegroundProcessWorkingDirectory(panelId:):
- The foreground PID comes from the kernel, not inference. Ghostty owns the PTY master, and
ghostty_surface_foreground_pidis a straighttcgetpgrp(master)(ghostty/src/pty.zig) — a single atomic read of the terminal's foreground process group. - The cwd is one
proc_pidinfo(PROC_PIDVNODEPATHINFO)on that PID.
Two reads total, no process enumeration.
The choice between the two directories is a pure function, TerminalWorkingDirectoryResolver.preferringForegroundDirectory(shellDirectory:foregroundDirectory:), added to the type that already owns this concern. The foreground cwd wins only when it resolves to a genuinely different directory, so at the prompt nothing changes and a symlinked shell cwd is preserved verbatim instead of being swapped for its realpath. An empty shell directory means directory provenance was deliberately withheld for that pane, so no foreground value may resurrect one.
Applied in the single shared focusedTerminalDirectoryURL(), so every open-in-IDE target benefits at once. The foreground read honors the same allowsLocalDirectoryFallback(panelId:) gate as the shell cwd and skips remote terminal surfaces, whose foreground process is a local ssh client and so says nothing about the remote directory being worked in.
Tests
TerminalForegroundDirectoryPreferenceTests (6 cases) covers the selection rule: foreground worktree preferred over repo root, unchanged at the prompt, foreground unavailable, blank foreground value, withheld provenance never resurrected, and a real symlinked-vs-realpath directory pair.
Localization
No user-facing strings added or changed (action titles untouched), so no Localizable.xcstrings / message-catalog updates are required.
History of this PR
This PR originally shipped a bespoke TerminalForegroundDirectoryResolver that reconstructed the foreground process group by enumerating every process (proc_listallpids + one proc_pidinfo per PID) and picking the group with .max() of e_tpgid. Review feedback on that approach was right on every count, and chasing it surfaced that the repo had since grown the correct primitive:
- Stale TTY name (Cursor Bugbot) — the resolver keyed off
surfaceTTYNames, the dictionary this repo explicitly documents as display-only metadata that must not be treated as evidence about the live runtime. Fixed by dropping TTY names entirely and going through the live surface. .max()over non-atomic samples (CodeRabbit) — unsound, since each PID is sampled by a separate syscall. Fixed by reading the value the kernel already maintains, viatcgetpgrp.- Blocking scan on
@MainActor(Greptile, CodeRabbit ×2) — the ~365-syscall scan is gone; two reads remain.
Net effect versus the previous revision: −253 lines, the new file and its pbxproj wiring removed, and the private → internal visibility widening of CmuxTopProcessSnapshot.allBSDProcesses() / fixedString(_:) reverted (project.pbxproj is now byte-identical to main).
Note the original PR description claimed cmux had no foreground-process-cwd reader anywhere. That was true when written, but TerminalWorkingDirectoryResolver landed in the meantime via #8691 (Dock terminal working-directory inheritance) and already solved the hard half.
Rebase
Rebased onto current main, which had moved a long way: focusedTerminalDirectoryURL() was refactored onto the new directory-provenance helpers (allowsLocalDirectoryFallback(panelId:), reportedPanelDirectory(panelId:), requestedWorkingDirectory), so the foreground preference is layered on that shape. The bug is unchanged on main — focusedTerminalDirectoryURL() still resolves only the shell's reported cwd — so the fix is still needed.
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.
Summary by cubic
Open Current Directory and other directory targets now follow the focused terminal foreground job’s cwd when it differs from the shell-reported cwd, fixing cases like claude --worktree opening the repository root instead of the active worktree. Prompt behavior, symlink-preserved paths, withheld provenance, and remote terminals remain unchanged.
- Reuses the existing live foreground cwd lookup through libghostty and
PROC_PIDVNODEPATHINFO. - Skips
tmuxandscreenclient directories because the pane shell reports the authoritative cwd. - Adds seven tests covering worktrees, prompt behavior, unavailable or blank values, withheld provenance, multiplexers, and symlinks.
Summary by CodeRabbit
- Bug Fixes
- Current-directory actions now more accurately open the directory used by the active foreground process in a terminal.
- Improved handling for worktrees and similar cases where the foreground process has moved to a different directory than the shell-reported location.
- Preserved existing behavior when directory information is unavailable, blank, remote, or equivalent through symlinks.
- Shell-reported directories remain in use when the foreground process is a terminal multiplexer or when its directory matches the shell’s.
[!NOTE]
Medium Risk
Changes which filesystem path is opened for an explicit user action; logic is gated on provenance and remote surfaces but wrong cwd selection could open the wrong project folder.
Overview
Open Current Directory (and related IDE/Finder targets) now resolves the focused pane’s directory by preferring the foreground job’s cwd over the shell’s OSC 7 path when they point at different locations—fixing cases like claude --worktree where the shell stays at the repo root while the agent runs in a worktree.
focusedTerminalDirectoryURL() still uses the existing provenance helpers for the shell path, then merges via new TerminalWorkingDirectoryResolver.preferringForegroundDirectory, which only overrides when realpaths differ so prompt behavior and symlink spellings stay unchanged. An empty shell directory still blocks any foreground value from resurrecting withheld provenance.
Foreground cwd is read through newly public Workspace.liveForegroundProcessWorkingDirectory(panelId:), with focusedTerminalForegroundDirectory(in:) skipping remote terminal surfaces and honoring the same local-directory fallback gate. Unit tests cover worktree preference, prompt/fallback, withheld provenance, and symlink preservation.
Reviewed by Cursor Bugbot for commit 27b3d11c3791db25e496866c085b4e26e9a35b97. Bugbot is set up for automated code reviews on this repo. Configure here.