fix(coding-agent): resolved symlinked paths before passing to brush

- Resolved symlinked paths before passing to brush shell to keep `pwd` output aligned with canonical Git worktree paths.
- Added `resolveShellCwd` helper that safely resolves symlinks and falls back to original path on error.
- Added test case verifying symlinked directories are canonicalized before execution.
This commit is contained in:
can1357
2026-03-09 15:39:05 +01:00
parent aa18e3f26d
commit 2d732bb033
5 changed files with 41 additions and 7 deletions
@@ -3,6 +3,7 @@
*
* Uses brush-core via native bindings for shell execution.
*/
import * as fs from "node:fs/promises";
import { Shell } from "@oh-my-pi/pi-natives";
import { Settings } from "../config/settings";
import { OutputSink } from "../session/streaming-output";
@@ -39,10 +40,23 @@ const HARD_TIMEOUT_GRACE_MS = 5_000;
const shellSessions = new Map<string, Shell>();
async function resolveShellCwd(cwd: string | undefined): Promise<string | undefined> {
if (!cwd) return undefined;
try {
// Brush preserves the working directory string verbatim, so resolve symlinks
// up front to keep `pwd` aligned with tools like `git worktree list`.
return await fs.realpath(cwd);
} catch {
return cwd;
}
}
export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
const settings = await Settings.init();
const { shell, env: shellEnv, prefix } = settings.getShellConfig();
const snapshotPath = shell.includes("bash") ? await getOrCreateSnapshot(shell, shellEnv) : null;
const commandCwd = await resolveShellCwd(options?.cwd);
// Apply command prefix if configured
const prefixedCommand = prefix ? `${prefix} ${command}` : command;
@@ -97,7 +111,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
const runPromise = shellSession.run(
{
command: finalCommand,
cwd: options?.cwd,
cwd: commandCwd,
env: options?.env ? { ...NON_INTERACTIVE_ENV, ...options.env } : NON_INTERACTIVE_ENV,
timeoutMs: options?.timeout,
signal,