MOB-183: mob.deploy verifies release bootfile on Android, not just ERTS - #79
Merged
Merged
Conversation
GenericJam
force-pushed
the
feat/mob-183-deploy-otp-check
branch
from
September 12, 2026 00:42
4991d18 to
41808e3
Compare
Owner
Author
|
Rebased onto current origin/master (52b0a72 — MOB-169). This branch had been cut from 21ba7f0 (0.7.1 bump); MOB-169 landed in between and my "check for parallel agent work" only looked at open PRs, not just-merged commits since the base. Rebase was clean — MOB-169 touches Post-rebase gates:
Sibling PR #78 (MOB-182 — refuse ambiguous multi-device deploy) was closed as obsoleted by MOB-169's stronger |
Non-native mix mob.deploy on Android already had a per-app OTP-runtime
guard that ran `ls .../otp/erts-*/bin/erl_child_setup` on the target
and refused a push when the file was missing. That covered "no OTP at
all" but missed the partial-runtime state a reporter hit: ERTS was
present but `otp/releases/*/start_clean.boot` was absent. The check
passed, the deploy reported "Deployed to 1 device(s) / Apps restarted",
and the app crash-dumped at boot with:
{'cannot get bootfile', .../otp/releases/29/start_clean.boot}
That's the state a device lands in when it was installed via `adb
install` without a matching `mix mob.deploy --native`, or when a prior
--native pushed ERTS but was interrupted before writing the release
tree. Third sighting of this failure family (see mob_dev#54).
The probe now checks BOTH globs in one run-as-wrapped `ls` and
classifies the output. The classifier is public
(`MobDev.Deployer.classify_android_runtime_ls/3`) so failure-mode
matrix is tested without a device — pattern used for adb / simctl
parsers elsewhere in this file. Classification order:
run_as_unavailable → erts_missing → bootfile_missing. erts_missing
wins the both-missing case: provisioning ERTS is what --native does
first, and pointing a caller at the bootfile they don't have yet
would send them chasing the wrong file.
The pre-review revision fell through to `:ok` on the exact adb-shell
arm the bug fires on — modern adb-shell (v2, default since Android 7)
forwards the inner shell's non-zero exit as the session code, so a
partial-runtime device would come back as `{:error, out}` from
`run_adb` and skip the classifier entirely. Fixed: `ensure_erts_on_device`
feeds `out` into the classifier regardless of status, and the
classifier's empty-output branch handles the true transport-failure
case as `:ok` (best-effort pass; downstream push failures still
surface the real problem).
Also recognises three `ls: <glob>: No such file` shapes seen in the
wild (Toybox default, older Toybox `not found`, GNU coreutils
`cannot access`). Anchoring on the exact glob is what lets the
classifier distinguish erts-missing vs bootfile-missing in the
both-missing case.
iOS is not covered by this change; its runtime lives host-side under
`~/.mob/runtime/ios-sim/<app>/`. Separate follow-up.
Decision record: decisions/2026-09-11-android-runtime-check-covers-bootfile.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ezeu2FEZfRajeoL4ywEUDJ
GenericJam
force-pushed
the
feat/mob-183-deploy-otp-check
branch
from
September 12, 2026 02:12
41808e3 to
fba78cc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
mix mob.deployon Android used to check forotp/erts-*/bin/erl_child_setupand pass when it was present — but the OTP release directory could still be missing, in which case the emulator loaded, the app crash-dumped at boot with{'cannot get bootfile', .../otp/releases/29/start_clean.boot}, and the deploy had reported success. Third sighting of this failure family (mob_dev#54).erts-*/bin/erl_child_setupANDreleases/*/start_clean.bootin one run-as-wrappedls. Classifier extracted to publicMobDev.Deployer.classify_android_runtime_ls/3— matrix testable without a device (same pattern asparse_devices_output/1,parse_simctl_json/1).ls: <glob>: No such filevariants (Toybox default, older Toyboxnot found, GNU coreutilscannot access). Anchors on the exact glob string so ERTS-missing vs bootfile-missing don't confuse each other.{:error, out}fromrun_adband skipped the classifier entirely. The classifier now runs onoutregardless of exit status; empty-out handles the true adb transport-failure case as best-effort:ok.Test plan
MobDev.Deployer.classify_android_runtime_ls/3— 8 tests covering: both present →:ok;run-as:prefix →run_as_unavailable; both missing →erts_missing(preferred over bootfile in the both-missing case); ERTS present + bootfile missing →bootfile_missing; Toyboxnot found; GNUcannot access; the MOB-183 regression fixture (adb-shell error arm with real ls output); empty/nil output →:ok(transport-failure fallback).mix testfull suite — 2431 passed / 10 excluded (integration).mix credo --strict— clean.~/.mob/runtime/ios-sim/<app>/(host-side, not device-side). Separate ticket.Linear: MOB-183
GH: closes mob_dev#54
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ezeu2FEZfRajeoL4ywEUDJ