From 24b281e25b06cecc80a4eec7a1fe95e98f51e973 Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Sun, 15 Mar 2026 16:05:54 -0600 Subject: [PATCH 1/3] Fix agent CWD detection to read from CLI process instead of shell tmux pane_current_path only tracks the shell's working directory, but agent CLIs (claude, codex, opencode) may cd into worktrees internally without updating the parent shell. This caused the UI to show "main repository" and the wrong branch for agents working in worktrees. Walk the process tree to find the agent CLI child process and read its actual CWD via lsof, falling back to pane_current_path when the CLI process can't be found. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/agents/manager.ts | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/agents/manager.ts b/src/agents/manager.ts index 6d5adcf0..f563e3a8 100644 --- a/src/agents/manager.ts +++ b/src/agents/manager.ts @@ -569,6 +569,16 @@ export class AgentManager { } try { + // First, try to resolve the CWD from the agent CLI process itself. + // tmux pane_current_path only tracks the shell's CWD, but agent CLIs + // (claude, codex, opencode) may cd internally without updating the shell. + const agentCwd = await this.resolveAgentProcessCwd(session); + if (agentCwd) { + this.runtimeCwdCache.set(cacheKey, { value: agentCwd, expiresAt: now + 10_000 }); + return agentCwd; + } + + // Fall back to tmux pane_current_path (the shell's CWD). const result = await runCommand("tmux", ["display-message", "-p", "-t", session, "#{pane_current_path}"], { allowedExitCodes: [0, 1], timeoutMs: 800 @@ -584,6 +594,75 @@ export class AgentManager { } } + /** + * Resolve the CWD of the agent CLI process (claude/codex/opencode) running + * inside a tmux pane. The CLI process may have cd'd into a worktree + * internally, which tmux's pane_current_path won't reflect. + */ + private async resolveAgentProcessCwd(session: string): Promise { + try { + // Get the PID of the tmux pane's shell process. + const pidResult = await runCommand( + "tmux", ["display-message", "-p", "-t", session, "#{pane_pid}"], + { allowedExitCodes: [0, 1], timeoutMs: 800 } + ); + const panePid = pidResult.stdout.trim(); + if (pidResult.exitCode !== 0 || !panePid) { + return null; + } + + // Find the agent CLI child process (claude, codex, or opencode). + const childrenResult = await runCommand( + "pgrep", ["-P", panePid], + { allowedExitCodes: [0, 1], timeoutMs: 800 } + ); + if (childrenResult.exitCode !== 0 || !childrenResult.stdout.trim()) { + return null; + } + + const childPids = childrenResult.stdout.trim().split("\n"); + let agentPid: string | null = null; + + for (const pid of childPids) { + const commResult = await runCommand( + "ps", ["-o", "comm=", "-p", pid.trim()], + { allowedExitCodes: [0, 1], timeoutMs: 800 } + ); + const comm = commResult.stdout.trim(); + // Match agent CLI binaries by basename. + const basename = comm.split("/").pop() ?? ""; + if (basename === "claude" || basename === "codex" || basename === "opencode") { + agentPid = pid.trim(); + break; + } + } + + if (!agentPid) { + return null; + } + + // Read the process's CWD via lsof (works on macOS and Linux). + const lsofResult = await runCommand( + "lsof", ["-a", "-p", agentPid, "-d", "cwd", "-Fn"], + { allowedExitCodes: [0, 1], timeoutMs: 800 } + ); + if (lsofResult.exitCode !== 0 || !lsofResult.stdout) { + return null; + } + + // lsof -Fn outputs lines like "p" and "n". Extract the path. + for (const line of lsofResult.stdout.split("\n")) { + if (line.startsWith("n/")) { + return line.slice(1); + } + } + + return null; + } catch { + return null; + } + } + private async startAgentSession( sessionName: string, cwd: string, From 77190bc4aa12ada578a9b246d63442211b1aae6d Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Sun, 15 Mar 2026 16:33:07 -0600 Subject: [PATCH 2/3] Add debug logging to resolveAgentProcessCwd Co-Authored-By: Claude Opus 4.6 (1M context) --- src/agents/manager.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/agents/manager.ts b/src/agents/manager.ts index f563e3a8..981fd999 100644 --- a/src/agents/manager.ts +++ b/src/agents/manager.ts @@ -608,6 +608,7 @@ export class AgentManager { ); const panePid = pidResult.stdout.trim(); if (pidResult.exitCode !== 0 || !panePid) { + this.logger.debug({ session }, "resolveAgentProcessCwd: no pane_pid"); return null; } @@ -617,6 +618,7 @@ export class AgentManager { { allowedExitCodes: [0, 1], timeoutMs: 800 } ); if (childrenResult.exitCode !== 0 || !childrenResult.stdout.trim()) { + this.logger.debug({ session, panePid }, "resolveAgentProcessCwd: no children"); return null; } @@ -638,6 +640,7 @@ export class AgentManager { } if (!agentPid) { + this.logger.debug({ session, panePid }, "resolveAgentProcessCwd: no agent CLI among children"); return null; } @@ -647,13 +650,16 @@ export class AgentManager { { allowedExitCodes: [0, 1], timeoutMs: 800 } ); if (lsofResult.exitCode !== 0 || !lsofResult.stdout) { + this.logger.debug({ session, agentPid }, "resolveAgentProcessCwd: lsof failed"); return null; } // lsof -Fn outputs lines like "p" and "n". Extract the path. for (const line of lsofResult.stdout.split("\n")) { if (line.startsWith("n/")) { - return line.slice(1); + const cwd = line.slice(1); + this.logger.debug({ session, agentPid, cwd }, "resolveAgentProcessCwd: resolved"); + return cwd; } } From 23f2b02d4393c4b0b3478c5b6f7ff0855a48e57c Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Sun, 15 Mar 2026 16:42:00 -0600 Subject: [PATCH 3/3] Simplify git section in agent sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the "Git" label, "Repository"/"Worktree" badge, and "main repository" text. Now shows only: 1. Branch name (with tooltip) 2. Worktree name — only when in a linked worktree (with tooltip showing the full worktree path) Co-Authored-By: Claude Opus 4.6 (1M context) --- web/src/components/app/agent-sidebar.tsx | 77 ++++++++++++------------ 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/web/src/components/app/agent-sidebar.tsx b/web/src/components/app/agent-sidebar.tsx index d51aea7c..2110bf47 100644 --- a/web/src/components/app/agent-sidebar.tsx +++ b/web/src/components/app/agent-sidebar.tsx @@ -364,47 +364,46 @@ export function AgentSidebarContent({
-
-
Git
- {agent.gitContext ? ( -
-
- - {agent.gitContext.branch} -
-
+ {agent.gitContext ? ( +
+ + +
+ + {agent.gitContext.branch} +
+
+ + Current branch + +
+ {agent.gitContext.isWorktree && ( + + +
+ + {agent.gitContext.worktreeName} +
+
+ + Linked worktree at {agent.gitContext.worktreePath} + +
+ )} +
+ ) : ( + + +
- - {agent.gitContext.isWorktree - ? `worktree: ${agent.gitContext.worktreeName}` - : "main repository"} - - - - - - {agent.gitContext.isWorktree ? "Worktree" : "Repository"} - - - - - {agent.gitContext.isWorktree - ? "This agent is currently in a linked git worktree checkout." - : "This agent is currently in the primary repository checkout."} - - + Not a git repository
-
- ) : ( -
- - Not a git repository -
- )} -
+ + + This directory is not inside a git repository + + + )}