Skip to content

Commit cb2e2e7

Browse files
committed
Restore redundant cd rejection in bash tool
Reverted from educational note back to rejection behavior. When a command tries to cd into the current working directory (e.g., 'cd /workspace && command'), the bash tool now rejects it with a clear error message. Changes: - Replaced educational note with redundant cd detection and rejection - Uses runtime.normalizePath() for cross-runtime path comparison - Works correctly for both local and SSH runtimes - Updated tests to verify rejection behavior
1 parent 526ab91 commit cb2e2e7

File tree

2 files changed

+35
-17
lines changed

2 files changed

+35
-17
lines changed

src/services/tools/bash.test.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ describe("SSH runtime redundant cd detection", () => {
11731173
};
11741174
}
11751175

1176-
it("should add educational note when command starts with cd", async () => {
1176+
it("should reject redundant cd when command cds to working directory", async () => {
11771177
const remoteCwd = "/remote/workspace/project/branch";
11781178
using testEnv = createTestBashToolWithSSH(remoteCwd);
11791179
const tool = testEnv.tool;
@@ -1185,27 +1185,33 @@ describe("SSH runtime redundant cd detection", () => {
11851185

11861186
const result = (await tool.execute!(args, mockToolCallOptions)) as BashToolResult;
11871187

1188-
// Command should execute (not blocked)
1189-
// But should include a note about cd behavior
1190-
if (result.success && "note" in result) {
1191-
expect(result.note).toContain("bash command starts in");
1192-
expect(result.note).toContain("do not persist");
1188+
// Should reject the redundant cd
1189+
expect(result.success).toBe(false);
1190+
if (!result.success) {
1191+
expect(result.error).toContain("Redundant cd to working directory");
1192+
expect(result.error).toContain("no cd needed");
1193+
expect(result.exitCode).toBe(-1);
11931194
}
11941195
});
11951196

1196-
it("should not add note when command does not start with cd", async () => {
1197+
it("should allow cd to different directory", async () => {
11971198
const remoteCwd = "/remote/workspace/project/branch";
11981199
using testEnv = createTestBashToolWithSSH(remoteCwd);
11991200
const tool = testEnv.tool;
12001201

12011202
const args: BashToolArgs = {
1202-
script: "echo test",
1203+
script: "cd /tmp && echo test",
12031204
timeout_secs: 5,
12041205
};
12051206

12061207
const result = (await tool.execute!(args, mockToolCallOptions)) as BashToolResult;
12071208

1208-
// Should not have a note field
1209-
expect(result).not.toHaveProperty("note");
1209+
// Should not be blocked (cd to a different directory is allowed)
1210+
// Note: Test runs locally so actual cd might fail, but we check it's not rejected
1211+
expect(result.exitCode).not.toBe(-1); // Not a rejection (-1)
1212+
if (!result.success) {
1213+
// If it failed, it should not be due to redundant cd detection
1214+
expect(result.error).not.toContain("Redundant cd");
1215+
}
12101216
});
12111217
});

src/services/tools/bash.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,25 @@ export const createBashTool: ToolFactory = (config: ToolConfiguration) => {
7676
let displayTruncated = false; // Hit 16KB display limit
7777
let fileTruncated = false; // Hit 100KB file limit
7878

79-
// Detect if command starts with cd - we'll add an educational note for the agent
80-
const scriptStartsWithCd = /^\s*cd\s/.test(script);
81-
const cdNote = scriptStartsWithCd
82-
? `Note: Each bash command starts in ${config.cwd}. Directory changes (cd) do not persist between commands.`
83-
: undefined;
79+
// Detect redundant cd to working directory
80+
// Match patterns like: "cd /path &&", "cd /path;", "cd '/path' &&", "cd \"/path\" &&"
81+
const cdPattern = /^\s*cd\s+['"]?([^'";&|]+)['"]?\s*[;&|]/;
82+
const match = cdPattern.exec(script);
83+
if (match) {
84+
const targetPath = match[1].trim();
85+
// Normalize paths for comparison using runtime's path resolution
86+
const normalizedTarget = config.runtime.normalizePath(targetPath, config.cwd);
87+
const normalizedCwd = config.runtime.normalizePath(".", config.cwd);
88+
89+
if (normalizedTarget === normalizedCwd) {
90+
return {
91+
success: false,
92+
error: `Redundant cd to working directory detected. The tool already runs in ${config.cwd} - no cd needed. Remove the 'cd ${targetPath}' prefix.`,
93+
exitCode: -1,
94+
wall_duration_ms: 0,
95+
};
96+
}
97+
}
8498

8599
// Execute using runtime interface (works for both local and SSH)
86100
const execStream = await config.runtime.exec(script, {
@@ -377,7 +391,6 @@ export const createBashTool: ToolFactory = (config: ToolConfiguration) => {
377391
output,
378392
exitCode: 0,
379393
wall_duration_ms,
380-
...(cdNote && { note: cdNote }),
381394
truncated: {
382395
reason: overflowReason ?? "unknown reason",
383396
totalLines: lines.length,
@@ -465,7 +478,6 @@ File will be automatically cleaned up when stream ends.`;
465478
output: lines.join("\n"),
466479
exitCode: 0,
467480
wall_duration_ms,
468-
...(cdNote && { note: cdNote }),
469481
});
470482
} else {
471483
resolveOnce({

0 commit comments

Comments
 (0)