@@ -61,8 +61,19 @@ impl AgentAdapter for ClaudeAdapter {
6161 self . root . iter ( ) . cloned ( ) . collect ( )
6262 }
6363 fn matches_process ( & self , cmd : & [ String ] , _exe : Option < & Path > ) -> bool {
64- cmd. iter ( )
65- . any ( |s| s. ends_with ( "/bin/claude" ) || s == "claude" || s. contains ( "/.claude/local/" ) )
64+ // Cross-platform match: handles Unix paths (`/usr/local/bin/claude`,
65+ // `/.claude/local/...`), bare names (`claude`, `claude.exe`,
66+ // `claude.cmd`), Windows backslash paths (`C:\...\.claude\local\...`),
67+ // and the npm-installed case where `node.exe` runs
68+ // `node_modules\@anthropic-ai\claude-code\cli.js`. The original
69+ // Unix-only matching silently rejected every running agent on
70+ // Windows so the Live Processes panel was always empty.
71+ cmd. iter ( ) . any ( |s| {
72+ super :: process_match:: path_ends_with ( s, "/bin/claude" )
73+ || super :: process_match:: bare_name_matches ( s, "claude" )
74+ || super :: process_match:: path_contains ( s, "/.claude/local/" )
75+ || super :: process_match:: path_contains ( s, "/@anthropic-ai/claude-code" )
76+ } )
6677 }
6778
6879 async fn parse_meta_fast ( & self , path : & Path ) -> Result < SessionMeta > {
@@ -1065,4 +1076,57 @@ mod tests {
10651076 . with_timezone ( & Utc ) ;
10661077 assert_eq ! ( meta. updated_at, Some ( expected) ) ;
10671078 }
1079+
1080+ #[ test]
1081+ fn matches_claude_cli_unix_paths ( ) {
1082+ let adapter = ClaudeAdapter :: new ( None ) ;
1083+ // Bare invocation (PATH-resolved).
1084+ assert ! ( adapter. matches_process( & [ "claude" . to_string( ) ] , None ) ) ;
1085+ // Standard /usr/local/bin form.
1086+ assert ! ( adapter. matches_process( & [ "/usr/local/bin/claude" . to_string( ) ] , None ) ) ;
1087+ // The `~/.claude/local/...` install (volta / nvm-style shim).
1088+ assert ! ( adapter. matches_process(
1089+ & [ "/Users/u/.claude/local/node_modules/.bin/claude" . to_string( ) ] ,
1090+ None ,
1091+ ) ) ;
1092+ // Should NOT match unrelated names.
1093+ assert ! ( !adapter. matches_process( & [ "claudette" . to_string( ) ] , None ) ) ;
1094+ assert ! ( !adapter. matches_process( & [ "/usr/bin/codex" . to_string( ) ] , None ) ) ;
1095+ }
1096+
1097+ #[ test]
1098+ fn matches_claude_cli_windows_paths ( ) {
1099+ // Regression: the Live Processes panel on Windows was always empty
1100+ // because the original Unix-only `s == "claude" || s.ends_with("/bin/claude")`
1101+ // check rejected every Windows-shaped argv entry. These forms are
1102+ // what `sysinfo` actually returns for a running Claude Code on
1103+ // Windows under different install methods.
1104+ let adapter = ClaudeAdapter :: new ( None ) ;
1105+ // Bare exe (PATH-resolved, native installer drops claude.exe).
1106+ assert ! ( adapter. matches_process( & [ "claude.exe" . to_string( ) ] , None ) ) ;
1107+ // Common case: npm-installed CLI runs as `node.exe` with the cli.js
1108+ // as argv[1]. The .cmd shim never appears as a long-running process,
1109+ // so we match the @anthropic-ai/claude-code package path instead.
1110+ assert ! ( adapter. matches_process(
1111+ & [
1112+ "C:\\ Program Files\\ nodejs\\ node.exe" . to_string( ) ,
1113+ "C:\\ Users\\ yjw\\ AppData\\ Roaming\\ npm\\ node_modules\\ @anthropic-ai\\ claude-code\\ cli.js" . to_string( ) ,
1114+ ] ,
1115+ None ,
1116+ ) ) ;
1117+ // Local `.claude\local\...` install with backslashes (the install
1118+ // script puts the runtime under `~/.claude/local/`).
1119+ assert ! ( adapter. matches_process(
1120+ & [
1121+ "C:\\ Users\\ yjw\\ .claude\\ local\\ node.exe" . to_string( ) ,
1122+ "C:\\ Users\\ yjw\\ .claude\\ local\\ node_modules\\ @anthropic-ai\\ claude-code\\ cli.js" . to_string( ) ,
1123+ ] ,
1124+ None ,
1125+ ) ) ;
1126+ // Mixed-separator path (some tools normalize even on Windows).
1127+ assert ! ( adapter. matches_process(
1128+ & [ "C:/Users/yjw/.claude/local/bin/claude.exe" . to_string( ) ] ,
1129+ None ,
1130+ ) ) ;
1131+ }
10681132}
0 commit comments