From 42630dbdcf3a46c0392e5c2a4dfc6ca663aebc77 Mon Sep 17 00:00:00 2001 From: escott- Date: Mon, 12 Oct 2026 20:37:56 +0000 Subject: [PATCH] fix(search): hosted results are rooted at the session's checkout, not the first machine's folder On a hosted gateway, search dropped the folder the session named in init unless a local mapping or index on the gateway named the project, which a hosted gateway can never read. Results were then rooted at the project's stored path, the folder of whichever machine first indexed it: on a machine with the checkout at /data/dev/maker/contextstream every hit came back as /home/core/dev/maker/contextstream/... and local enrichment reported missing_root for that folder. scoped_session_folder_path also accepts the project init resolved for the session's folder (state.project_id). A session scoped to another project still never roots results. Co-Authored-By: Claude Opus 5.5 --- crates/mcp-tools/src/domains/search.rs | 9 ++++++ crates/mcp-tools/src/domains/search_tests.rs | 29 ++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/crates/mcp-tools/src/domains/search.rs b/crates/mcp-tools/src/domains/search.rs index a2e5986..d9590ca 100644 --- a/crates/mcp-tools/src/domains/search.rs +++ b/crates/mcp-tools/src/domains/search.rs @@ -5804,9 +5804,16 @@ fn current_dir_search_root() -> Option { best.map(|(_, _, normalized)| normalized) } +/// The session's folder, when it is a checkout of the project being searched. +/// +/// `session_project_id` is the project `init` resolved for that folder. On a +/// hosted gateway it is the only evidence: the local mapping and index cannot +/// be read there, and without it results were rooted at the project's stored +/// path, the folder of whichever machine first indexed it. fn scoped_session_folder_path<'a>( session_folder_path: Option<&'a str>, session_folder_project_id: Option, + session_project_id: Option, local_index_project_id: Option, target_project_id: Option, local_project_root: Option<&'a str>, @@ -5817,6 +5824,7 @@ fn scoped_session_folder_path<'a>( }; if session_folder_project_id == Some(target_project_id) + || session_project_id == Some(target_project_id) || local_index_project_id == Some(target_project_id) { return Some(session_path); @@ -11196,6 +11204,7 @@ impl SearchTool { let scoped_session_folder_path = scoped_session_folder_path( session_folder_path.as_deref(), resolved_folder_project_id, + state.project_id, local_index_project_id, target_project_id, local_project_root.as_deref(), diff --git a/crates/mcp-tools/src/domains/search_tests.rs b/crates/mcp-tools/src/domains/search_tests.rs index b4698ca..7130589 100644 --- a/crates/mcp-tools/src/domains/search_tests.rs +++ b/crates/mcp-tools/src/domains/search_tests.rs @@ -4484,6 +4484,7 @@ mod auto_mode_tests { Some("/srv/projects/mcp"), Some(mcp), Some(mcp), + Some(mcp), Some(other_project), Some("/srv/example-repo"), ), @@ -4491,6 +4492,33 @@ mod auto_mode_tests { ); } + #[test] + fn test_hosted_session_folder_roots_results_for_its_own_project() { + // A hosted gateway reads no local mapping or index; init resolved the + // session's folder to the project being searched, so results are + // rooted there, not at the folder of the machine that first indexed + // the project. + let project = uuid::Uuid::parse_str("22222222-2222-4222-8222-222222222222").unwrap(); + let session = scoped_session_folder_path( + Some("/data/dev/maker/contextstream"), + None, + Some(project), + None, + Some(project), + None, + ); + assert_eq!(session, Some("/data/dev/maker/contextstream")); + assert_eq!( + resolve_effective_folder_path( + session, + Some("/home/core/dev/maker/contextstream"), + None, + false, + ), + Some("/data/dev/maker/contextstream".to_string()) + ); + } + #[test] fn test_scoped_session_folder_path_keeps_nested_target_project_root() { let canonical_project = @@ -4500,6 +4528,7 @@ mod auto_mode_tests { Some("/srv/example-repo/web"), None, None, + None, Some(canonical_project), Some("/srv/example-repo"), ),