From 3afcafe3fc0eb6d2aec28ec8d7900a455f873cc3 Mon Sep 17 00:00:00 2001 From: Prohect Date: Wed, 6 May 2026 14:26:09 +0100 Subject: [PATCH] agent: Return clear error when read_file tool path is a directory (#54303) Fixes #54244 When the `read_file` tool is called with a path that points to a directory instead of a file, it now returns a clear, actionable error message telling the agent to use `list_directory` instead. Previously the tool would fail with an unhelpful generic error. Now it explicitly checks whether the path is a directory before attempting to read it. A test covering this case is also included. Release Notes: - Fixed `read_file` tool returning an unhelpful error when given a directory path; it now suggests using `list_directory` instead. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- crates/agent/src/tools/read_file_tool.rs | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/agent/src/tools/read_file_tool.rs b/crates/agent/src/tools/read_file_tool.rs index 4fa27114c8e2ea77e93afdc3dd9f0c710907a15c..da0cbddb86aadb85706e276afe6c8075aeba9a45 100644 --- a/crates/agent/src/tools/read_file_tool.rs +++ b/crates/agent/src/tools/read_file_tool.rs @@ -184,6 +184,13 @@ impl AgentTool for ReadFileTool { anyhow::Ok(()) }).map_err(tool_content_err)?; + if fs.is_dir(&abs_path).await { + return Err(tool_content_err(format!( + "{} is a directory, not a file. Use the list_directory tool to explore directory contents.", + &input.path + ))); + } + if let Some(canonical_target) = &symlink_canonical_target { let authorize = cx.update(|cx| { authorize_symlink_access( @@ -356,6 +363,39 @@ mod test { use std::sync::Arc; use util::path; + #[gpui::test] + async fn test_read_directory_path(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/root"), + json!({ + "some_dir": {} + }), + ) + .await; + let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; + let action_log = cx.new(|_| ActionLog::new(project.clone())); + let tool = Arc::new(ReadFileTool::new(project, action_log, true)); + let (event_stream, _) = ToolCallEventStream::test(); + + let result = cx + .update(|cx| { + let input = ReadFileToolInput { + path: "root/some_dir".to_string(), + start_line: None, + end_line: None, + }; + tool.run(ToolInput::resolved(input), event_stream, cx) + }) + .await; + assert_eq!( + error_text(result.unwrap_err()), + "root/some_dir is a directory, not a file. Use the list_directory tool to explore directory contents." + ); + } + #[gpui::test] async fn test_read_nonexistent_file(cx: &mut TestAppContext) { init_test(cx);