@@ -144,7 +144,7 @@ fn get_claude_dir() -> Result<PathBuf> {
144144 . context ( "Could not find ~/.claude directory" )
145145}
146146
147- /// Gets the actual project path by reading the cwd from the first JSONL entry
147+ /// Gets the actual project path by reading the cwd from the JSONL entries
148148fn get_project_path_from_sessions ( project_dir : & PathBuf ) -> Result < String , String > {
149149 // Try to read any JSONL file in the directory
150150 let entries = fs:: read_dir ( project_dir)
@@ -154,14 +154,20 @@ fn get_project_path_from_sessions(project_dir: &PathBuf) -> Result<String, Strin
154154 if let Ok ( entry) = entry {
155155 let path = entry. path ( ) ;
156156 if path. is_file ( ) && path. extension ( ) . and_then ( |s| s. to_str ( ) ) == Some ( "jsonl" ) {
157- // Read the first line of the JSONL file
157+ // Read the JSONL file and find the first line with a valid cwd
158158 if let Ok ( file) = fs:: File :: open ( & path) {
159159 let reader = BufReader :: new ( file) ;
160- if let Some ( Ok ( first_line) ) = reader. lines ( ) . next ( ) {
161- // Parse the JSON and extract cwd
162- if let Ok ( json) = serde_json:: from_str :: < serde_json:: Value > ( & first_line) {
163- if let Some ( cwd) = json. get ( "cwd" ) . and_then ( |v| v. as_str ( ) ) {
164- return Ok ( cwd. to_string ( ) ) ;
160+ // Check first few lines instead of just the first line
161+ // Some session files may have null cwd in the first line
162+ for line in reader. lines ( ) . take ( 10 ) {
163+ if let Ok ( line_content) = line {
164+ // Parse the JSON and extract cwd
165+ if let Ok ( json) = serde_json:: from_str :: < serde_json:: Value > ( & line_content) {
166+ if let Some ( cwd) = json. get ( "cwd" ) . and_then ( |v| v. as_str ( ) ) {
167+ if !cwd. is_empty ( ) {
168+ return Ok ( cwd. to_string ( ) ) ;
169+ }
170+ }
165171 }
166172 }
167173 }
@@ -2156,3 +2162,141 @@ pub async fn validate_hook_command(command: String) -> Result<serde_json::Value,
21562162 Err ( e) => Err ( format ! ( "Failed to validate command: {}" , e) )
21572163 }
21582164}
2165+
2166+ #[ cfg( test) ]
2167+ mod tests {
2168+ use super :: * ;
2169+ use std:: io:: Write ;
2170+ use tempfile:: TempDir ;
2171+
2172+ /// Helper function to create a test session file
2173+ fn create_test_session_file ( dir : & PathBuf , filename : & str , content : & str ) -> Result < ( ) , std:: io:: Error > {
2174+ let file_path = dir. join ( filename) ;
2175+ let mut file = fs:: File :: create ( file_path) ?;
2176+ file. write_all ( content. as_bytes ( ) ) ?;
2177+ Ok ( ( ) )
2178+ }
2179+
2180+ #[ test]
2181+ fn test_get_project_path_from_sessions_normal_case ( ) {
2182+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2183+ let project_dir = temp_dir. path ( ) . to_path_buf ( ) ;
2184+
2185+ // Create a session file with cwd on the first line
2186+ let content = r#"{"type":"system","cwd":"/Users/test/my-project"}"# ;
2187+ create_test_session_file ( & project_dir, "session1.jsonl" , content) . unwrap ( ) ;
2188+
2189+ let result = get_project_path_from_sessions ( & project_dir) ;
2190+ assert ! ( result. is_ok( ) ) ;
2191+ assert_eq ! ( result. unwrap( ) , "/Users/test/my-project" ) ;
2192+ }
2193+
2194+ #[ test]
2195+ fn test_get_project_path_from_sessions_with_hyphen ( ) {
2196+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2197+ let project_dir = temp_dir. path ( ) . to_path_buf ( ) ;
2198+
2199+ // This is the bug scenario - project path contains hyphens
2200+ let content = r#"{"type":"system","cwd":"/Users/test/data-discovery"}"# ;
2201+ create_test_session_file ( & project_dir, "session1.jsonl" , content) . unwrap ( ) ;
2202+
2203+ let result = get_project_path_from_sessions ( & project_dir) ;
2204+ assert ! ( result. is_ok( ) ) ;
2205+ assert_eq ! ( result. unwrap( ) , "/Users/test/data-discovery" ) ;
2206+ }
2207+
2208+ #[ test]
2209+ fn test_get_project_path_from_sessions_null_cwd_first_line ( ) {
2210+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2211+ let project_dir = temp_dir. path ( ) . to_path_buf ( ) ;
2212+
2213+ // First line has null cwd, second line has valid path
2214+ let content = format ! (
2215+ "{}\n {}" ,
2216+ r#"{"type":"system","cwd":null}"# ,
2217+ r#"{"type":"system","cwd":"/Users/test/valid-path"}"#
2218+ ) ;
2219+ create_test_session_file ( & project_dir, "session1.jsonl" , & content) . unwrap ( ) ;
2220+
2221+ let result = get_project_path_from_sessions ( & project_dir) ;
2222+ assert ! ( result. is_ok( ) ) ;
2223+ assert_eq ! ( result. unwrap( ) , "/Users/test/valid-path" ) ;
2224+ }
2225+
2226+ #[ test]
2227+ fn test_get_project_path_from_sessions_multiple_lines ( ) {
2228+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2229+ let project_dir = temp_dir. path ( ) . to_path_buf ( ) ;
2230+
2231+ // Multiple lines with cwd appearing on line 5
2232+ let content = format ! (
2233+ "{}\n {}\n {}\n {}\n {}" ,
2234+ r#"{"type":"other"}"# ,
2235+ r#"{"type":"system","cwd":null}"# ,
2236+ r#"{"type":"message"}"# ,
2237+ r#"{"type":"system"}"# ,
2238+ r#"{"type":"system","cwd":"/Users/test/project"}"#
2239+ ) ;
2240+ create_test_session_file ( & project_dir, "session1.jsonl" , & content) . unwrap ( ) ;
2241+
2242+ let result = get_project_path_from_sessions ( & project_dir) ;
2243+ assert ! ( result. is_ok( ) ) ;
2244+ assert_eq ! ( result. unwrap( ) , "/Users/test/project" ) ;
2245+ }
2246+
2247+ #[ test]
2248+ fn test_get_project_path_from_sessions_empty_dir ( ) {
2249+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2250+ let project_dir = temp_dir. path ( ) . to_path_buf ( ) ;
2251+
2252+ let result = get_project_path_from_sessions ( & project_dir) ;
2253+ assert ! ( result. is_err( ) ) ;
2254+ assert_eq ! ( result. unwrap_err( ) , "Could not determine project path from session files" ) ;
2255+ }
2256+
2257+ #[ test]
2258+ fn test_get_project_path_from_sessions_no_jsonl_files ( ) {
2259+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2260+ let project_dir = temp_dir. path ( ) . to_path_buf ( ) ;
2261+
2262+ // Create a non-JSONL file
2263+ create_test_session_file ( & project_dir, "readme.txt" , "Some text" ) . unwrap ( ) ;
2264+
2265+ let result = get_project_path_from_sessions ( & project_dir) ;
2266+ assert ! ( result. is_err( ) ) ;
2267+ }
2268+
2269+ #[ test]
2270+ fn test_get_project_path_from_sessions_no_cwd ( ) {
2271+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2272+ let project_dir = temp_dir. path ( ) . to_path_buf ( ) ;
2273+
2274+ // JSONL file without any cwd field
2275+ let content = format ! (
2276+ "{}\n {}\n {}" ,
2277+ r#"{"type":"system"}"# ,
2278+ r#"{"type":"message"}"# ,
2279+ r#"{"type":"other"}"#
2280+ ) ;
2281+ create_test_session_file ( & project_dir, "session1.jsonl" , & content) . unwrap ( ) ;
2282+
2283+ let result = get_project_path_from_sessions ( & project_dir) ;
2284+ assert ! ( result. is_err( ) ) ;
2285+ }
2286+
2287+ #[ test]
2288+ fn test_get_project_path_from_sessions_multiple_sessions ( ) {
2289+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2290+ let project_dir = temp_dir. path ( ) . to_path_buf ( ) ;
2291+
2292+ // Create multiple session files - should return from first valid one
2293+ create_test_session_file ( & project_dir, "session1.jsonl" , r#"{"type":"system","cwd":"/path1"}"# ) . unwrap ( ) ;
2294+ create_test_session_file ( & project_dir, "session2.jsonl" , r#"{"type":"system","cwd":"/path2"}"# ) . unwrap ( ) ;
2295+
2296+ let result = get_project_path_from_sessions ( & project_dir) ;
2297+ assert ! ( result. is_ok( ) ) ;
2298+ // Should get one of the paths (implementation checks first file it finds)
2299+ let path = result. unwrap ( ) ;
2300+ assert ! ( path == "/path1" || path == "/path2" ) ;
2301+ }
2302+ }
0 commit comments