@@ -16,12 +16,29 @@ pub fn run() {
1616
1717fn remove_binary ( home : & std:: path:: Path ) {
1818 #[ cfg( not( target_os = "windows" ) ) ]
19- let candidates = [ home. join ( ".local/bin/cship" ) , home. join ( ".cargo/bin/cship" ) ] ;
19+ let candidates = vec ! [ home. join( ".local/bin/cship" ) , home. join( ".cargo/bin/cship" ) ] ;
2020 #[ cfg( target_os = "windows" ) ]
21- let candidates = [
22- home. join ( ".cargo/bin/cship.exe" ) ,
23- home. join ( r".local\bin\cship.exe" ) ,
24- ] ;
21+ let candidates = {
22+ let mut v: Vec < std:: path:: PathBuf > = Vec :: new ( ) ;
23+ match std:: env:: var ( "LOCALAPPDATA" ) {
24+ Ok ( local_app_data) => {
25+ v. push (
26+ std:: path:: Path :: new ( & local_app_data)
27+ . join ( "Programs" )
28+ . join ( "cship" )
29+ . join ( "cship.exe" ) ,
30+ ) ;
31+ }
32+ Err ( _) => {
33+ tracing:: warn!(
34+ "LOCALAPPDATA env var not set; skipping %LOCALAPPDATA%\\ Programs\\ cship\\ cship.exe candidate"
35+ ) ;
36+ }
37+ }
38+ v. push ( home. join ( ".cargo/bin/cship.exe" ) ) ;
39+ v. push ( home. join ( r".local\bin\cship.exe" ) ) ;
40+ v
41+ } ;
2542 for bin in candidates {
2643 if bin. exists ( ) {
2744 match std:: fs:: remove_file ( & bin) {
@@ -35,7 +52,21 @@ fn remove_binary(home: &std::path::Path) {
3552}
3653
3754fn remove_statusline_from_settings ( home : & std:: path:: Path ) {
55+ #[ cfg( target_os = "windows" ) ]
56+ let path = match std:: env:: var ( "APPDATA" ) {
57+ Ok ( app_data) => std:: path:: Path :: new ( & app_data)
58+ . join ( "Claude" )
59+ . join ( "settings.json" ) ,
60+ Err ( _) => {
61+ tracing:: warn!(
62+ "APPDATA env var not set; falling back to ~/.claude/settings.json for settings path"
63+ ) ;
64+ home. join ( ".claude/settings.json" )
65+ }
66+ } ;
67+ #[ cfg( not( target_os = "windows" ) ) ]
3868 let path = home. join ( ".claude/settings.json" ) ;
69+
3970 if !path. exists ( ) {
4071 println ! ( "settings.json not found — skipping." ) ;
4172 return ;
@@ -131,9 +162,35 @@ mod tests {
131162 let cargo_path = cargo_bin. join ( bin_name) ;
132163 std:: fs:: write ( & cargo_path, b"fake binary" ) . unwrap ( ) ;
133164
165+ // On Windows, also test the LOCALAPPDATA candidate path
166+ #[ cfg( target_os = "windows" ) ]
167+ let ( tmp_local, localappdata_bin_path) = {
168+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
169+ let programs_dir = tmp. path ( ) . join ( "Programs" ) . join ( "cship" ) ;
170+ std:: fs:: create_dir_all ( & programs_dir) . unwrap ( ) ;
171+ let bin_path = programs_dir. join ( "cship.exe" ) ;
172+ std:: fs:: write ( & bin_path, b"fake binary" ) . unwrap ( ) ;
173+ // Point LOCALAPPDATA to our temp dir so remove_binary finds it
174+ // SAFETY: guarded by HOME_MUTEX; no other threads read LOCALAPPDATA concurrently.
175+ unsafe { std:: env:: set_var ( "LOCALAPPDATA" , tmp. path ( ) ) } ;
176+ ( tmp, bin_path)
177+ } ;
178+
134179 remove_binary ( home) ;
180+
135181 assert ! ( !local_path. exists( ) ) ;
136182 assert ! ( !cargo_path. exists( ) ) ;
183+
184+ #[ cfg( target_os = "windows" ) ]
185+ {
186+ assert ! (
187+ !localappdata_bin_path. exists( ) ,
188+ "LOCALAPPDATA binary should be removed on Windows"
189+ ) ;
190+ // SAFETY: guarded by HOME_MUTEX; no other threads read LOCALAPPDATA concurrently.
191+ unsafe { std:: env:: remove_var ( "LOCALAPPDATA" ) } ;
192+ drop ( tmp_local) ;
193+ }
137194 } ) ;
138195 }
139196
@@ -171,6 +228,43 @@ mod tests {
171228 } ) ;
172229 }
173230
231+ #[ test]
232+ #[ cfg( target_os = "windows" ) ]
233+ fn test_remove_statusline_uses_appdata_on_windows ( ) {
234+ with_tempdir ( |home| {
235+ // Create a temp APPDATA directory with Claude/settings.json
236+ let tmp_appdata = tempfile:: tempdir ( ) . unwrap ( ) ;
237+ let claude_dir = tmp_appdata. path ( ) . join ( "Claude" ) ;
238+ std:: fs:: create_dir_all ( & claude_dir) . unwrap ( ) ;
239+ let settings_path = claude_dir. join ( "settings.json" ) ;
240+ std:: fs:: write (
241+ & settings_path,
242+ r#"{"statusline":"cship","otherKey":"value"}"# ,
243+ )
244+ . unwrap ( ) ;
245+
246+ // SAFETY: guarded by HOME_MUTEX; no other threads read APPDATA concurrently.
247+ unsafe { std:: env:: set_var ( "APPDATA" , tmp_appdata. path ( ) ) } ;
248+
249+ remove_statusline_from_settings ( home) ;
250+
251+ let content = std:: fs:: read_to_string ( & settings_path) . unwrap ( ) ;
252+ let parsed: serde_json:: Value = serde_json:: from_str ( & content) . unwrap ( ) ;
253+ assert ! (
254+ parsed. get( "statusline" ) . is_none( ) ,
255+ "statusline key should be removed from APPDATA path on Windows"
256+ ) ;
257+ assert_eq ! (
258+ parsed. get( "otherKey" ) . and_then( |v| v. as_str( ) ) ,
259+ Some ( "value" ) ,
260+ "other keys should be preserved"
261+ ) ;
262+
263+ // SAFETY: guarded by HOME_MUTEX; no other threads read APPDATA concurrently.
264+ unsafe { std:: env:: remove_var ( "APPDATA" ) } ;
265+ } ) ;
266+ }
267+
174268 #[ test]
175269 fn test_remove_statusline_absent_key ( ) {
176270 with_tempdir ( |home| {
@@ -244,6 +338,7 @@ mod tests {
244338 // Should print message and return, not panic or touch root paths
245339 run ( ) ;
246340 // Restore to avoid poisoning other tests
341+ // SAFETY: guarded by HOME_MUTEX; no other threads read CLAUDE_HOME concurrently.
247342 unsafe { std:: env:: remove_var ( "CLAUDE_HOME" ) } ;
248343 }
249344}
0 commit comments