Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
- name: Check for unsafe rm usage
run: |
echo "Checking for unsafe rm patterns..."
if grep -r "rm -rf" --include="*.sh" lib/ | grep -v "safe_remove\|validate_path\|# "; then
if grep -r "rm -rf" --include="*.sh" lib/ | grep -v "safe_remove\|validate_path\|# \|echo "; then
echo "✗ Unsafe rm -rf usage found"
exit 1
fi
Expand Down
22 changes: 22 additions & 0 deletions lib/clean/apps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ clean_orphaned_app_data() {
if [[ -d "$claude_support_dir" ]]; then
while IFS= read -r -d '' claude_vm_bundle; do
if is_claude_vm_bundle_orphaned "$claude_vm_bundle" "$installed_bundles"; then
if is_path_whitelisted "$claude_vm_bundle"; then
debug_log "Skipping whitelisted orphan: $claude_vm_bundle"
continue
fi
local claude_vm_size_kb
claude_vm_size_kb=$(get_path_size_kb "$claude_vm_bundle")
if [[ -n "$claude_vm_size_kb" && "$claude_vm_size_kb" != "0" ]]; then
Expand Down Expand Up @@ -388,6 +392,10 @@ clean_orphaned_app_data() {
bundle_id="${bundle_id%.binarycookies}"
bundle_id="${bundle_id%.plist}"
if is_bundle_orphaned "$bundle_id" "$match" "$installed_bundles"; then
if is_path_whitelisted "$match"; then
debug_log "Skipping whitelisted orphan: $match"
continue
fi
local size_kb
size_kb=$(get_path_size_kb "$match")
if [[ -z "$size_kb" || "$size_kb" == "0" ]]; then
Expand Down Expand Up @@ -596,6 +604,20 @@ clean_orphaned_system_services() {

stop_section_spinner

# Drop whitelisted entries before reporting/cleaning.
if [[ $orphaned_count -gt 0 && ${#WHITELIST_PATTERNS[@]} -gt 0 ]]; then
local -a kept_files=()
for orphan_file in "${orphaned_files[@]}"; do
if is_path_whitelisted "$orphan_file"; then
debug_log "Skipping whitelisted orphan service: $orphan_file"
continue
fi
kept_files+=("$orphan_file")
done
orphaned_count=${#kept_files[@]}
orphaned_files=("${kept_files[@]}")
fi

# Report and clean
if [[ $orphaned_count -gt 0 ]]; then
echo -e " ${GRAY}${ICON_WARNING}${NC} Found $orphaned_count orphaned system services"
Expand Down
69 changes: 69 additions & 0 deletions tests/clean_apps.bats
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,75 @@ EOF
}


@test "clean_orphaned_app_data honors WHITELIST_PATTERNS for Claude VM bundle" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/apps.sh"

scan_installed_apps() { : > "$1"; }
mdfind() { return 0; }
pgrep() { return 1; }
run_with_timeout() { shift; "$@"; }
get_file_mtime() { echo 0; }
get_path_size_kb() { echo 4; }
safe_clean() { echo "UNEXPECTED_CLEAN:$2"; rm -rf "$1"; }
start_section_spinner() { :; }
stop_section_spinner() { :; }

mkdir -p "$HOME/Library/Caches"
mkdir -p "$HOME/Library/Application Support/Claude/vm_bundles/claudevm.bundle"
echo "vm data" > "$HOME/Library/Application Support/Claude/vm_bundles/claudevm.bundle/rootfs.img"

WHITELIST_PATTERNS=("$HOME/Library/Application Support/Claude/vm_bundles/claudevm.bundle")

clean_orphaned_app_data

if [[ -d "$HOME/Library/Application Support/Claude/vm_bundles/claudevm.bundle" ]]; then
echo "PASS: Claude VM preserved by whitelist"
fi
EOF

[ "$status" -eq 0 ]
[[ "$output" != *"UNEXPECTED_CLEAN"* ]]
[[ "$output" == *"PASS: Claude VM preserved by whitelist"* ]]
}

@test "clean_orphaned_app_data honors WHITELIST_PATTERNS for orphaned caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/apps.sh"

scan_installed_apps() { : > "$1"; }
is_bundle_orphaned() { return 0; }
is_claude_vm_bundle_orphaned() { return 1; }
mdfind() { return 0; }
pgrep() { return 1; }
run_with_timeout() { shift; "$@"; }
get_file_mtime() { echo 0; }
get_path_size_kb() { echo 4; }
safe_clean() { echo "UNEXPECTED_CLEAN:$2"; rm -rf "$1"; }
start_section_spinner() { :; }
stop_section_spinner() { :; }

mkdir -p "$HOME/Library/Caches/com.devtool.localbuild"
echo "c" > "$HOME/Library/Caches/com.devtool.localbuild/data"

WHITELIST_PATTERNS=("$HOME/Library/Caches/com.devtool.localbuild")

clean_orphaned_app_data

if [[ -d "$HOME/Library/Caches/com.devtool.localbuild" ]]; then
echo "PASS: whitelisted orphan cache preserved"
fi
EOF

[ "$status" -eq 0 ]
[[ "$output" != *"UNEXPECTED_CLEAN"* ]]
[[ "$output" == *"PASS: whitelisted orphan cache preserved"* ]]
}

@test "is_critical_system_component matches known system services" {
run bash --noprofile --norc <<'EOF'
set -euo pipefail
Expand Down
Loading