Skip to content

MOB-183: mob.deploy verifies release bootfile on Android, not just ERTS - #79

Merged
GenericJam merged 1 commit into
masterfrom
feat/mob-183-deploy-otp-check
Sep 12, 2026
Merged

GenericJam merged 1 commit into
masterfrom
feat/mob-183-deploy-otp-check

Conversation

@GenericJam

Copy link
Copy Markdown
Owner

Summary

  • Non-native mix mob.deploy on Android used to check for otp/erts-*/bin/erl_child_setup and 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).
  • Probe now verifies BOTH erts-*/bin/erl_child_setup AND releases/*/start_clean.boot in one run-as-wrapped ls. Classifier extracted to public MobDev.Deployer.classify_android_runtime_ls/3 — matrix testable without a device (same pattern as parse_devices_output/1, parse_simctl_json/1).
  • Recognises three ls: <glob>: No such file variants (Toybox default, older Toybox not found, GNU coreutils cannot access). Anchors on the exact glob string so ERTS-missing vs bootfile-missing don't confuse each other.
  • Fixed the pre-review revision's silent-pass: 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 came back as {:error, out} from run_adb and skipped the classifier entirely. The classifier now runs on out regardless 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; Toybox not found; GNU cannot access; the MOB-183 regression fixture (adb-shell error arm with real ls output); empty/nil output → :ok (transport-failure fallback).
  • mix test full suite — 2431 passed / 10 excluded (integration).
  • mix credo --strict — clean.
  • Adversarial pre-commit review by fresh subagent found the classifier-never-runs blocking issue; fix applied and specifically regressed with a fixture-based test.
  • iOS follow-up: the sim variant of this failure family lives under ~/.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

@GenericJam
GenericJam force-pushed the feat/mob-183-deploy-otp-check branch from 4991d18 to 41808e3 Compare September 12, 2026 00:42
@GenericJam

Copy link
Copy Markdown
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 deployer.ex but nowhere near ensure_erts_on_device, so no code-level interaction.

Post-rebase gates:

  • mix compile --warnings-as-errors — clean
  • mix test — 2455 passed, 10 excluded (integration)
  • mix credo --strict — clean

Sibling PR #78 (MOB-182 — refuse ambiguous multi-device deploy) was closed as obsoleted by MOB-169's stronger --all-devices / --all-physical gate. This PR (MOB-183 — runtime bootfile check) is unrelated to that surface and stands on its own.

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
GenericJam force-pushed the feat/mob-183-deploy-otp-check branch from 41808e3 to fba78cc Compare September 12, 2026 02:12
@GenericJam
GenericJam merged commit 807b02a into master Sep 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant