Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/services/tools/file_edit_insert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,4 +329,77 @@ describe("file_edit_insert tool", () => {
expect(result.error).toContain("beyond file length");
}
});

it("should handle content with trailing newline correctly (no double newlines)", async () => {
// This test verifies the fix for the terminal-bench "hello-world" bug
// where content with \n at the end was getting an extra newline added
using testEnv = createTestFileEditInsertTool({ cwd: testDir });
const tool = testEnv.tool;
const args: FileEditInsertToolArgs = {
file_path: "newfile.txt",
line_offset: 0,
content: "Hello, world!\n", // Content already has trailing newline
create: true,
};

// Execute
const result = (await tool.execute!(args, mockToolCallOptions)) as FileEditInsertToolResult;

// Assert
expect(result.success).toBe(true);

const fileContent = await fs.readFile(path.join(testDir, "newfile.txt"), "utf-8");
// Should NOT have double newline - the trailing \n in content should be preserved as-is
expect(fileContent).toBe("Hello, world!\n");
expect(fileContent).not.toBe("Hello, world!\n\n");
});

it("should handle multiline content with trailing newline", async () => {
// Setup
const initialContent = "line1\nline2";
await fs.writeFile(testFilePath, initialContent);

using testEnv = createTestFileEditInsertTool({ cwd: testDir });
const tool = testEnv.tool;
const args: FileEditInsertToolArgs = {
file_path: "test.txt",
line_offset: 1,
content: "INSERTED1\nINSERTED2\n", // Multiline with trailing newline
};

// Execute
const result = (await tool.execute!(args, mockToolCallOptions)) as FileEditInsertToolResult;

// Assert
expect(result.success).toBe(true);

const updatedContent = await fs.readFile(testFilePath, "utf-8");
// Should respect the trailing newline in content
expect(updatedContent).toBe("line1\nINSERTED1\nINSERTED2\nline2");
});

it("should preserve trailing newline when appending to file without trailing newline", async () => {
// Regression test for Codex feedback: when inserting at EOF with content ending in \n,
// the newline should be preserved even if the original file doesn't end with one
const initialContent = "line1\nline2"; // No trailing newline
await fs.writeFile(testFilePath, initialContent);

using testEnv = createTestFileEditInsertTool({ cwd: testDir });
const tool = testEnv.tool;
const args: FileEditInsertToolArgs = {
file_path: "test.txt",
line_offset: 2, // Append at end
content: "line3\n", // With trailing newline
};

// Execute
const result = (await tool.execute!(args, mockToolCallOptions)) as FileEditInsertToolResult;

// Assert
expect(result.success).toBe(true);

const updatedContent = await fs.readFile(testFilePath, "utf-8");
// Should preserve the trailing newline from content even at EOF
expect(updatedContent).toBe("line1\nline2\nline3\n");
});
});
15 changes: 14 additions & 1 deletion src/services/tools/file_edit_insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,20 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration)
};
}

const newLines = [...lines.slice(0, line_offset), content, ...lines.slice(line_offset)];
// Handle newline behavior:
// - If content ends with \n and we're not at EOF, strip it (join will add it back)
// - If content ends with \n and we're at EOF, keep it (join won't add trailing newline)
// - If content doesn't end with \n, keep as-is (join will add newlines between lines)
const contentEndsWithNewline = content.endsWith("\n");
const insertingAtEnd = line_offset === lines.length;
const shouldStripTrailingNewline = contentEndsWithNewline && !insertingAtEnd;
const normalizedContent = shouldStripTrailingNewline ? content.slice(0, -1) : content;

const newLines = [
...lines.slice(0, line_offset),
normalizedContent,
...lines.slice(line_offset),
];
const newContent = newLines.join("\n");

return {
Expand Down