From e6ee9ec490c1f05db11af1482de80796627148ea Mon Sep 17 00:00:00 2001 From: root Date: Wed, 18 Mar 2026 03:15:39 +0100 Subject: [PATCH] fix macOS update false positives --- lib/check/all.sh | 25 ++++++++++++++++++ tests/clean_system_maintenance.bats | 40 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/lib/check/all.sh b/lib/check/all.sh index f38021b..6d2eabd 100644 --- a/lib/check/all.sh +++ b/lib/check/all.sh @@ -386,9 +386,34 @@ check_macos_update() { # Prefer avoiding false negatives: if the system indicates updates are pending, # only clear the flag when softwareupdate returns a list without any update entries. + # However, macOS doesn't distinguish between system and App Store updates in the + # LastRecommendedUpdatesAvailable counter, so we additionally require that at least + # one listed update is a macOS system update before showing a macOS update warning. if [[ $sw_status -eq 0 && -n "$sw_output" ]]; then if ! echo "$sw_output" | grep -qE '^[[:space:]]*\*'; then + # No update entries at all updates_available="false" + else + # softwareupdate output may include both macOS and App Store updates. + # Treat only entries whose Label contains "macOS" as system updates. + local has_macos_update="false" + while IFS= read -r line; do + [[ "$line" =~ ^[[:space:]]*\* ]] || continue + local label="$line" + label="${label#*Label: }" + label="${label%%,*}" + local lower_label + lower_label=$(printf '%s' "$label" | tr '[:upper:]' '[:lower:]') + if [[ "$lower_label" == macos* || "$lower_label" == *"macos "* || "$lower_label" == *" macos"* ]]; then + has_macos_update="true" + break + fi + done <<< "$sw_output" + + if [[ "$has_macos_update" != "true" ]]; then + # Only App Store updates are pending – don't flag macOS as outdated + updates_available="false" + fi fi fi fi diff --git a/tests/clean_system_maintenance.bats b/tests/clean_system_maintenance.bats index 0626f2d..5bc0348 100644 --- a/tests/clean_system_maintenance.bats +++ b/tests/clean_system_maintenance.bats @@ -669,6 +669,46 @@ EOF [[ "$output" != *"SHOULD_NOT_CALL_SOFTWAREUPDATE"* ]] } +@test "check_macos_update does not flag macOS when only App Store updates are pending" { + run bash --noprofile --norc << 'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/check/all.sh" + +defaults() { echo "1"; } + +run_with_timeout() { + local timeout="${1:-}" + shift + if [[ "$timeout" != "10" ]]; then + echo "BAD_TIMEOUT:$timeout" + return 124 + fi + if [[ "${1:-}" == "softwareupdate" && "${2:-}" == "-l" && "${3:-}" == "--no-scan" ]]; then + cat <<'OUT' +Software Update Tool + +Software Update found the following new or updated software: + * Label: Xcode-15.3 +OUT + return 0 + fi + return 124 +} + +start_inline_spinner(){ :; } +stop_inline_spinner(){ :; } + +check_macos_update +echo "MACOS_UPDATE_AVAILABLE=$MACOS_UPDATE_AVAILABLE" +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"System up to date"* ]] + [[ "$output" == *"MACOS_UPDATE_AVAILABLE=false"* ]] + [[ "$output" != *"BAD_TIMEOUT:"* ]] +} + @test "check_macos_update outputs debug info when MO_DEBUG set" { run bash --noprofile --norc << 'EOF' set -euo pipefail