From 97ee138de80167707e79c9c64176ad6831d81ceb Mon Sep 17 00:00:00 2001 From: kevin9327 Date: Sun, 30 Aug 2026 19:07:28 +0900 Subject: [PATCH] fix(tools): reject empty old_string in EditNotebook StrReplace rejects an empty `old_string`, but the EditNotebook cell-edit path did not. Because `str::match_indices("")` matches at every byte boundary, editing a non-empty cell with an empty `old_string` failed with a misleading "old_string is not unique in the notebook cell; found N occurrences" error, and editing an empty cell silently prepended `new_string`. Add the same guard StrReplace already uses so both edit tools reject an empty `old_string` consistently. Co-Authored-By: Claude Opus 4.8 --- server/src/cursor/tools/edit.rs | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/server/src/cursor/tools/edit.rs b/server/src/cursor/tools/edit.rs index 3637f05..09146d7 100644 --- a/server/src/cursor/tools/edit.rs +++ b/server/src/cursor/tools/edit.rs @@ -183,6 +183,9 @@ fn edit_notebook(call: &ToolCall, before: &str) -> std::result::Result return Err("old_string was not found in the notebook cell".into()), @@ -241,3 +244,49 @@ fn normalized(value: &str) -> String { .flat_map(char::to_lowercase) .collect() } + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::edit_notebook; + use crate::model::ToolCall; + + fn notebook_call(old_string: &str) -> ToolCall { + ToolCall { + index: 0, + call_id: "call".into(), + model_call_id: "model".into(), + name: "EditNotebook".into(), + arguments_text: String::new(), + arguments: json!({ + "target_notebook": "/notebook.ipynb", + "cell_idx": 0, + "old_string": old_string, + "new_string": "replacement", + }), + } + } + + fn single_cell_notebook() -> String { + json!({ + "cells": [{"cell_type": "code", "source": ["print('hi')\n"]}], + }) + .to_string() + } + + #[test] + fn edit_notebook_rejects_empty_old_string() { + // StrReplace rejects an empty old_string; EditNotebook must do the same + // instead of prepending new_string (empty cell) or reporting a + // misleading "not unique" error (non-empty cell). + let error = edit_notebook(¬ebook_call(""), &single_cell_notebook()).unwrap_err(); + assert_eq!(error, "old_string must not be empty"); + } + + #[test] + fn edit_notebook_replaces_a_unique_old_string() { + let edited = edit_notebook(¬ebook_call("hi"), &single_cell_notebook()).unwrap(); + assert!(edited.contains("print('replacement')")); + } +}