Universal-Debloater-Alliance/universal-android-debloater-next-generation · error
SDK version numeral must be valid
Error message
SDK version numeral must be valid
What it means
get_android_sdk shells out to `adb shell getprop ro.build.version.sdk` and parses the returned string into a u8. If adb returns anything that is not a valid u8 (empty output, an error string, or a number larger than u8), the `.unwrap_or`-style map_or only covers the command failing — a successful command with unparseable output hits `.expect("SDK version numeral must be valid")` and panics. The library assumes getprop always yields a plain numeric SDK level when the command succeeds.
Solutions
- Replace .expect with graceful fallback: `sdk.parse().unwrap_or(0)` so an invalid value is treated the same as a failed adb command.
- Change the return type to Result<u8, String> and propagate the parse error to the caller instead of panicking.
- Change the return type to u32 (or parse as u32 then clamp) to remove the u8 overflow failure mode.
- Log the raw getprop output when parsing fails so the malformed device state is diagnosable.
Example fix
// before
.map_or(0, |sdk| {
sdk.parse().expect("SDK version numeral must be valid")
})
// after
.map_or(0, |sdk| sdk.trim().parse().unwrap_or(0)) Defensive patterns
Strategy: fallback
Validate before calling
let out = AdbCommand::new().shell(serial).getprop("ro.build.version.sdk").unwrap_or_default();
let sdk: Option<u8> = out.trim().parse().ok();
if sdk.is_none() { eprintln!("device {serial} returned non-numeric SDK: {out:?}"); } Type guard
fn is_valid_sdk(s: &str) -> bool { s.trim().parse::<u8>().is_ok() } Try / catch
match sdk_str.trim().parse::<u8>() {
Ok(v) => v,
Err(e) => { warn!("bad SDK value {sdk_str:?}: {e}"); 0 }
} Prevention
- Never .expect on parse results from external processes; always fall back to a sentinel like 0.
- Trim adb/getprop output before parsing (it can carry trailing whitespace or CR on Windows).
- Use u32 for SDK levels to survive future Android API growth past 255.
When it happens
Trigger: Calling get_android_sdk(device_serial) when the device's `ro.build.version.sdk` property is non-numeric or exceeds 255: e.g. a device booting into a broken state, a future Android release with SDK > 255, or a device whose getprop output includes extra whitespace/suffix text.
Common situations: Connecting to emulators/custom ROMs with malformed build props; testing against a hypothetical Android version whose API level exceeds 255 (u8 overflow); devices in a half-booted or recovery-adjacent state where getprop succeeds but returns garbage.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- There must be at least 1 ':'-separated component
- There must be 1 tab after serial
- string assumed to be UID numeral
- Unable to parse
- {e}
AI-assisted analysis of Universal-Debloater-Alliance/universal-android-debloater-next-generation@64465c850c (2026-09-12).
Data as JSON: /api/errors/18ea0407d5196113.
Report an issue: GitHub.
Appendix: source
Thrown at crates/uad-core/src/sync.rs:272
AdbCommand::new()
.shell(serial)
.getprop("ro.product.brand")
// `trim` is just-in-case
.map(|s| s.trim().to_string())
.unwrap_or_default()
}
/// Get Android SDK version by querying the
// `ro.build.version.sdk` property or defaulting to 0.
///
/// If `device_serial` is empty, it lets ADB choose the default device.
#[must_use]
pub fn get_android_sdk(device_serial: &str) -> u8 {
AdbCommand::new()
.shell(device_serial)
.getprop("ro.build.version.sdk")
.map_or(0, |sdk| {
sdk.parse().expect("SDK version numeral must be valid")
})
}
/// Capture the current state of a package across all non-protected users.
/// This is used to detect cross-user behavior by comparing before and after states.
///
/// Only includes users where the package exists (Some state). Users where the package
/// doesn't exist (None) are not tracked.
#[must_use]
pub fn capture_cross_user_states(
package_name: &str,
device_serial: &str,
target_user_id: u16,
phone: &Phone,
) -> Vec<(u16, PackageState)> {
phone
.user_list
.iter()View on GitHub (pinned to 64465c850c)