Skip to content

fix(tmux): run tmux CLI from stable cwd and verify pane starts in workspace#2984

Open
harshitsinghbhandari wants to merge 1 commit into
mainfrom
ao/agent-orchestrator-108/tmux-stable-cwd
Open

fix(tmux): run tmux CLI from stable cwd and verify pane starts in workspace#2984
harshitsinghbhandari wants to merge 1 commit into
mainfrom
ao/agent-orchestrator-108/tmux-stable-cwd

Conversation

@harshitsinghbhandari

Copy link
Copy Markdown
Collaborator

Summary

Fixes the tmux poisoned-cwd bug that strands worker panes in deleted ShipIt directories (root-caused live by inode matching; related: #2775).

Root cause. execRunner.Run called exec.CommandContext without setting cmd.Dir, so every tmux CLI invocation inherited the daemon's own cwd. The first tmux call after boot auto-starts the persistent tmux server, permanently pinning the server's cwd to whatever the daemon had. After a Squirrel/ShipIt auto-update that can be a staging dir that gets deleted (observed: /var/folders/.../ShipIt.zThlb2VO/...app/Contents/Resources; even the healthy case is the app bundle Resources dir, which ShipIt swaps every update). tmux has a silent fallback: when new-session -c <dir> cannot be resolved at spawn time, the pane starts in the server's cwd instead of erroring. A session hit that fallback and its agent died with a context-free ENOENT: Bun could not find a file plus shell-init getcwd errors.

Reproduced on a scratch tmux socket: tmux new-session -d -c <deleted-dir> exits 0 and the pane silently starts elsewhere; display-message -p '#{pane_current_path}' exposes the mismatch.

Changes

  • execRunner.Run now sets cmd.Dir = os.TempDir(), so the auto-started tmux server is never pinned to a path an app update can delete. (tmux.New is constructed config-free in runtimeselect, so the temp dir is the clean stable choice over plumbing the data dir through.)
  • Create now verifies the pane's #{pane_current_path} matches cfg.WorkspacePath after the liveness check, resolving symlinks so macOS /var vs /private/var compares equal. On mismatch it destroys the session and fails with an error naming both paths and pointing at the server-cwd fallback, replacing today's silent fallback plus downstream raw ENOENT.
  • New displayPaneCwdArgs builder in commands.go, following the existing arg-builder pattern.

Tests

  • New: TestCreateFailsLoudlyWhenPaneCwdMismatches (fake runner reports a pane cwd that is not the workspace; asserts loud error naming both paths + kill-session cleanup), TestSamePathResolvesSymlinks, TestExecRunnerRunsFromStableDir.
  • Updated TestCommandBuilders and the existing Create tests for the new sixth call.
  • Ran: gofmt (clean), go vet, go build ./..., go test ./internal/adapters/runtime/tmux/ (42 pass, including the real-tmux integration tests, which now exercise the verification through macOS /var aliasing end to end).
  • Full go test ./...: 4 pre-existing internal/cli spawn-test failures reproduce identically on clean upstream/main on this machine (live AO daemon state interferes); unrelated to this change.

Risks / follow-ups

  • The verification only runs at Create. IsAlive and SendMessage deliberately do not probe pane cwd in this PR (kept minimal per scope); a follow-up could add a cwd probe to the reaper path to catch a server whose panes were poisoned before this fix shipped.
  • Create now fails where it previously "succeeded" with a doomed pane. That is the intended behavior change: a loud, actionable error instead of a stranded agent.

🤖 Generated with Claude Code

…kspace

The first tmux CLI call after daemon boot auto-starts the persistent tmux
server, which inherits and keeps the daemon's own cwd. After a Squirrel/ShipIt
auto-update that cwd can be a deleted staging dir; tmux then silently falls
back to the server cwd whenever new-session -c cannot resolve, stranding
worker panes in a deleted directory and killing agents with a context-free
Bun ENOENT (issue #2775, root-caused by inode matching).

- execRunner.Run now sets cmd.Dir to os.TempDir() so the tmux server is never
  pinned to a path an app update can delete.
- Create verifies the pane's #{pane_current_path} matches cfg.WorkspacePath
  (symlink-resolved, so macOS /var vs /private/var compares equal) and fails
  with an error naming both paths instead of leaving the silent fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant