From 4a4ab3c102067f7a24f737987e80fbb7cedbe86c Mon Sep 17 00:00:00 2001 From: Cedric <20724723+code-ric@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:36:48 +0200 Subject: [PATCH] fix(wrapper): follow the shell working directory in the wrapper process (#129) Fixes #124. Since #78 the shell reports `IRIS_CWD` on `chpwd`, but the wrapper only passes it to `spec.SetCWD` for suggestions. Its own working directory never moves, so tools that read the pane's foreground process to locate the shell resolve the wrong path after a `cd`. I hit this with herdr, which stopped detecting the git repository of a pane once IRIS was in it. tmux uses the same lookup for `pane_current_path`. The absolute path check matches what `spec.SetCWD` already does, so both treat a malformed value the same way. The `os.Chdir` error is dropped because the path comes from the shell integration, the user cannot act on the failure, and keeping the last known directory is better than stopping the loop. `go test ./...` passes. I tested this end to end on macOS only: the wrapper followed the `cd`, and herdr picked the repository back up. One limitation: the outer launcher process keeps its start directory, only the wrapper running `runWrapper` follows. That was enough for herdr, which reads the wrapper. --- root/wrapper.go | 8 ++++++++ root/wrapper_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 root/wrapper_test.go diff --git a/root/wrapper.go b/root/wrapper.go index e8ad519..cdcbf61 100644 --- a/root/wrapper.go +++ b/root/wrapper.go @@ -117,6 +117,13 @@ func restoreTerminal() { } } +func syncProcessCWD(cwd string) { + if !filepath.IsAbs(cwd) { + return + } + _ = os.Chdir(cwd) +} + // runWrapper sets up the pty environment, launches the shell, // and manages the main input loop to provide real-time suggestions // it handles raw terminal mode to intercept keystrokes and @@ -494,6 +501,7 @@ func runWrapper() { if cwd, ok := strings.CutPrefix(query, "IRIS_CWD:"); ok { spec.SetCWD(cwd) + syncProcessCWD(cwd) continue } diff --git a/root/wrapper_test.go b/root/wrapper_test.go new file mode 100644 index 0000000..01bffa4 --- /dev/null +++ b/root/wrapper_test.go @@ -0,0 +1,49 @@ +package root + +import ( + "os" + "path/filepath" + "testing" +) + +func wantDir(t *testing.T, path string) string { + t.Helper() + resolved, err := filepath.EvalSymlinks(path) + if err != nil { + t.Fatal(err) + } + return resolved +} + +func currentDir(t *testing.T) string { + t.Helper() + cwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + return wantDir(t, cwd) +} + +func TestSyncProcessCWDFollowsShell(t *testing.T) { + shellDir := t.TempDir() + t.Chdir(t.TempDir()) + + syncProcessCWD(shellDir) + + if got := currentDir(t); got != wantDir(t, shellDir) { + t.Fatalf("os.Getwd() = %q, want %q", got, wantDir(t, shellDir)) + } +} + +func TestSyncProcessCWDKeepsDirectoryOnBadPath(t *testing.T) { + launcherDir := t.TempDir() + t.Chdir(launcherDir) + + for _, cwd := range []string{"", "relative/path", filepath.Join(launcherDir, "does-not-exist")} { + syncProcessCWD(cwd) + + if got := currentDir(t); got != wantDir(t, launcherDir) { + t.Fatalf("syncProcessCWD(%q) moved the process to %q, want %q", cwd, got, wantDir(t, launcherDir)) + } + } +}