{"record":{"id":"3e1557602abb5208","repo":"Hmbown/CodeWhale","slug":"no-proc-self-maps-row-contains-the-updater-marker","errorCode":null,"errorMessage":"no /proc/self/maps row contains the updater marker","messagePattern":"no /proc/self/maps row contains the updater marker","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/cli/src/update.rs","lineNumber":374,"sourceCode":"            .context(\"loaded-image mapping has an invalid device minor number\")?;\n        if path.is_empty() {\n            bail!(\"loaded-image mapping for updater marker has no pathname\");\n        }\n\n        let mapping = AndroidImageMapping {\n            start,\n            end,\n            device_major,\n            device_minor,\n            inode,\n            path: PathBuf::from(path),\n        };\n        if matching.replace(mapping).is_some() {\n            bail!(\"multiple /proc/self/maps rows contain the updater marker\");\n        }\n    }\n\n    matching.ok_or_else(|| anyhow!(\"no /proc/self/maps row contains the updater marker\"))\n}\n\n#[cfg(all(test, unix))]\nfn resolve_android_loaded_executable_report(\n    maps: &str,\n    marker: u64,\n    dladdr_path: &Path,\n) -> Result<PathBuf> {\n    Ok(android_loaded_executable_proof_report(maps, marker, dladdr_path)?.path)\n}\n\n#[cfg(any(target_os = \"android\", all(test, unix)))]\nfn android_loaded_executable_proof_report(\n    maps: &str,\n    marker: u64,\n    dladdr_path: &Path,\n) -> Result<AndroidExecutableProof> {\n    let mapping = parse_android_image_mapping(maps, marker)?;","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/cli/src/update.rs#L356-L392","documentation":"On Android the updater must prove which on-disk file the running executable was loaded from. It scans /proc/self/maps for a row whose [start, end) range contains a marker address taken from the CLI's own code image. This error means the scan finished without any row covering the marker, so the loaded image could not be attributed to a file and self-update is refused.","triggerScenarios":"parse_android_image_mapping runs with the /proc/self/maps text and the marker address; it fires when no row satisfies start <= marker < end: the maps snapshot does not describe this process, the image is only in anonymous/JIT mappings (no pathname), or a test passes fixture maps whose ranges all miss the marker.","commonSituations":"Android 10+ replaces app-data executable paths in /proc/self/maps with unnamed mappings; running under instrumentation (gdbserver, wrap.sh) or from an interpreter such as app_process; emulators with unusual layouts; unit tests with hand-written maps fixtures that forget to cover the marker address.","solutions":["Inspect the live process (adb shell cat /proc/<pid>/maps): if the executable rows have no pathname, that Android build hides them and self-update from that location is unsupported; install the binary on a mapped path instead","If this is a test fixture, add a row like '7f0000000000-7f0000001000 r-xp 00000000 fd:01 1337 /data/local/tmp/codewhale' whose range covers the marker, has executable permission, nonzero inode, and a pathname","Update to a newer Codewhale build: the Android proof logic also consults dladdr as a second authority and may succeed where the maps scan alone cannot","If the process runs from memfd or an interpreter, launch the real binary file so it gets a file-backed mapping"],"exampleFix":"// test fixture: before (marker 0x7f0000000042 is not covered)\nlet maps = \"00000000-00010000 r-xp 00000000 fd:01 10 /system/bin/linker64\\n\";\n\n// after: add a row that covers the marker\nlet maps = \"00000000-00010000 r-xp 00000000 fd:01 10 /system/bin/linker64\\n\\n7f0000000000-7f0000001000 r-xp 00000000 fd:01 1337 /data/local/tmp/codewhale\\n\";","handlingStrategy":"try-catch","validationCode":"// Before invoking the proof path, verify the maps text covers the marker:\nfn maps_cover_marker(maps: &str, marker: u64) -> bool {\n    maps.lines().any(|line| {\n        let mut fields = line.split_whitespace();\n        let range = match fields.next() { Some(r) => r, None => return false };\n        let (s, e) = match range.split_once('-') { Some(p) => p, None => return false };\n        match (u64::from_str_radix(s, 16), u64::from_str_radix(e, 16)) {\n            (Ok(s), Ok(e)) => s <= marker && marker < e,\n            _ => false,\n        }\n    })\n}","typeGuard":null,"tryCatchPattern":"Treat the Err as 'cannot self-update on this Android layout': report to the user and skip replacement rather than retrying. The maps/marker relationship is stable for the life of the process, so retries cannot succeed.","preventionTips":["Run the CLI from a file-backed executable with a pathname-mapped mapping on Android instead of memfd or interpreter loading","Keep test maps fixtures synchronized with the marker address constant"],"tags":["android","proc-maps","self-update","executable-detection"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}