Merge PR #4784: fix(coding-agent): persist bash shortcut cwd changes (@jeffscottward)

This commit is contained in:
can1357
2026-07-20 22:50:05 +02:00
5 changed files with 490 additions and 3 deletions
@@ -150,6 +150,53 @@ function isBashShell(shell: string): boolean {
return basename.includes("bash");
}
const UNSUPPORTED_UNQUOTED_CD_CHARS = "\\$`;&|<>(){}*?[]!#\"'";
function hasUnsupportedUnquotedCdSyntax(value: string): boolean {
for (const char of value) {
if (/\s/.test(char) || UNSUPPORTED_UNQUOTED_CD_CHARS.includes(char)) return true;
}
return false;
}
export function isPersistentShellCdCommand(command: string): boolean {
if (/[\r\n]/.test(command)) return false;
const trimmed = command.trim();
if (trimmed === "cd") return true;
if (!trimmed.startsWith("cd") || !/[ \t]/.test(trimmed[2] ?? "")) return false;
let rest = trimmed.slice(2).trim();
if (rest === "" || rest === "--") return true;
let hasOptionTerminator = false;
if (/^--[ \t]/.test(rest)) {
hasOptionTerminator = true;
rest = rest.slice(2).trimStart();
}
if (rest === "") return true;
const quote = rest[0];
let target: string;
let quoted = false;
if (quote === `"` || quote === "'") {
if (rest.length < 2 || rest[rest.length - 1] !== quote) return false;
target = rest.slice(1, -1);
if (target.includes(quote)) return false;
if (quote === `"` && /[\\$`\r\n]/.test(target)) return false;
quoted = true;
} else {
if (hasUnsupportedUnquotedCdSyntax(rest)) return false;
target = rest;
}
if (target === "") return false;
if (/^[+-]\d+$/.test(target)) return false;
if (!hasOptionTerminator && target.startsWith("-") && target !== "-") return false;
if (!quoted && target.startsWith("~") && target !== "~" && !target.startsWith("~/")) return false;
return true;
}
function needsInteractiveShellArg(shell: string): boolean {
const basename = shellBasename(shell);
return basename.includes("zsh") || basename.includes("fish");
@@ -236,8 +283,9 @@ export async function executeBash(command: string, options?: BashExecutorOptions
// Apply command prefix if configured
const prefixedCommand = prefix ? `${prefix} ${command}` : command;
const runCdInPersistentShell = options?.useUserShell === true && !prefix && isPersistentShellCdCommand(command);
const finalCommand =
options?.useUserShell === true && !bashShell
options?.useUserShell === true && !bashShell && !runCdInPersistentShell
? buildUserShellCommand(shell, args, prefixedCommand)
: prefixedCommand;