Answer First

A crash on a cloud phone is not a failed script step: it is a distinct failure mode with its own evidence trail, and confusing the two is why “app keeps crashing on cloud phone” problems drag on. When Android shows the “App keeps stopping” force-close dialog, the app’s process has terminated — the only question is which layer killed it: the app, the device image, or your script.

Definition: A crash is an unhandled termination of the app process — an uncaught exception, a native fault, or a resource-exhaustion kill — that Android reports with a modal force-close dialog and records in logcat as a FATAL EXCEPTION (or native signal) stack trace. A script failure is different: the automation misses its own expectation — a selector not found, a timeout, an assertion mismatch — while the app stays alive. Process death versus unmet expectation — different evidence, different fixes.

Why: Classification decides the fix. An app bug will not yield to any retry; a device-image problem calls for reinstalling the app or rebuilding the image; a script-triggered crash means slowing the step down. And the dialog sits unseen: one crash can silently burn a device’s entire task window in an unattended batch. Crash detection and a clean stop belong in every unattended run.

Example: A batch script opens a shopping app, adds an item, and checks out. On run 14 of 40, the wrong app shows “keeps stopping.” Every tap after that landed on “Close app,” and the run died with a generic “element not found.” Without a dialog screenshot, a logcat excerpt, timestamps, and the step number, you cannot tell whether the app died, the image ran out of memory, or a double-tap during initialization pushed it over the edge.

Key Facts

  • A crash is process death, recorded in logcat as a FATAL EXCEPTION stack trace (or a native signal such as SIGSEGV) — pull it with the logcat command-line tool.
  • The “keeps stopping” dialog is modal — your taps land on it, not the app — so further taps after a crash are guaranteed to fail.
  • Three classes: app bug, device-image problem, script-triggered native crash — the evidence identifies which.
  • Device-image problems include low RAM or storage — and apps that deliberately refuse to run in virtualized environments, a behavior OWASP’s MASVS documents. An instant “keeps stopping” can be policy, not defect.
  • Capture at crash time: dialog screenshot, logcat excerpt, timestamps, script step, image and app versions. Logcat is a ring buffer — the trace is gone minutes later.
  • The move after classification is one of three: bounded retry, human review, or reinstalling the app on the device image.

Expert Explanation

Classifying the dialog: app, device image, or script

App-level crash. The app dies at the same point on every image and Android version; a human replaying the steps reproduces it; the stack trace points into the app’s own code. The fix lives upstream of your automation, so escalate with evidence rather than retry — unless the crash is intermittent.

Device-image problem. The crash follows a specific image, Android version, or device profile. Causes include low RAM or storage, conflicting preinstalled apps, a stale image build, and apps that detect the virtualized environment and self-terminate on purpose — a behavior OWASP’s MASVS documents precisely because some apps refuse to run off physical hardware. An app that force-closes within a second of launch on every cloud phone but runs fine on hardware is suspect before your script is. Fix the environment: reinstall the app, move the workload to another image, or update the image.

Script-triggered native crash. The crash correlates with a specific step: tapping before initialization completes, navigating faster than the app settles, or memory accumulating across a long session. The tell is the step number — replay the step and it recurs; slow it down and it disappears. This is the most common class in unattended batches, and the one you can fix in the script.

What to capture at crash time

Capture the whole evidence set in one atomic action, before anything else:

  • Dialog screenshot — names the app (“keeps stopping” versus “isn’t responding”). Some apps set secure flags that blank captures; rely on logcat then.
  • Logcat excerpt — the FATAL EXCEPTION block plus the ~50 lines before it, and the exception type: OutOfMemoryError is a resource problem, NullPointerException is app logic, SIGSEGV is native code. Pull it immediately — the buffer rolls over.
  • Timestamps — device time at the crash, script step time, run ID.
  • Script step — step number, action, and the UI state you expected.
  • Environment — image ID, Android version, app package and version, device profile.

In QCCBot, the evidence log attaches all of this to a single run record so a reviewer can reconstruct the crash; monitoring should treat a crash dialog as its own signal — AI Guardian flags and the human-review queue should see “crash, step 7, evidence attached,” not a generic “task failed.”

Add a crash-guard step

After every critical action, assert the expected app is still the foreground activity (or probe for a known element). When the check fails:

  1. Capture the evidence set in one shot.
  2. Mark the run crashed, with step and evidence attached.
  3. Stop cleanly — no further taps, no dialog dismissal, no blind continuation.
  4. Release the device for human review, or re-run from the last checkpoint if the step is idempotent.

Never hammer the dead app: retrying taps against a force-closed process is wasted motion and destroys the evidence — the dialog may be dismissed or the app relaunches half-initialized, and the original trace is gone. If you allow auto-recovery, keep it bounded: one relaunch, resume from the last checkpoint, only for steps safe to repeat. Everything else goes to review. A run that cannot proceed should stop loudly, not fail quietly.

Retry, escalate, or reinstall

  • Retry transient crashes: one occurrence, an idempotent step, no pattern, clean relaunch.
  • Escalate to human review reproducible crashes, crashes with possible side effects (a half-completed action, account state touched mid-crash), or evidence pointing at the app itself. Reproducible crashes are bugs — they need a person or an upstream fix, not another attempt.
  • Reinstall the app on the device image when the crash follows every launch, the app’s data is corrupt, or the installed version is wrong. If a clean reinstall still crashes, the problem is the image build or the app itself — move the workload.

Practical limits

No automation layer can fix an app bug from outside, and some cases stay ambiguous: obfuscated stacks hide the failing class, some apps log nothing, secure flags blank the screenshot, and some managed images restrict adb. Decide in advance which evidence sources are guaranteed on your fleet, and design the crash-guard around those.

Decision Framework

What you observeMost likely classFirst move
Dialog within a second of launch, every launch, every imageApp-level, or deliberate virtualization refusalCheck logcat; try the app on hardware; reinstall on one image
Crash at the same step, across different imagesScript-triggeredSlow the step, add settle waits, make it idempotent, add a crash-guard
Crash only on one image; manual use is fine elsewhereDevice imageReinstall the app or rebuild the image; check RAM and storage
One crash mid-run, no pattern, clean relaunchTransientOne bounded retry; escalate if it recurs
Silent process death, no dialogResource kill (low memory)Check logcat for the low-memory killer; reduce concurrency or move to a larger image

Key Takeaways

  • A crash is process death with its own evidence trail — never log it as a generic script failure.
  • Classify before you act: app bug, device-image problem, and script-triggered crash each have a different fix.
  • Capture the full evidence set at crash time: dialog screenshot, logcat excerpt, timestamps, script step, image and app versions.
  • Add a crash-guard step so runs stop cleanly instead of hammering a dead app.
  • Retry only transient, idempotent failures; escalate reproducible ones; reinstall the app when the crash follows every launch.

FAQ

Q: How do I know whether it’s the app or my script causing the crash? A: Reproduce it. If the crash follows a specific script step and disappears when you slow that step down, it is script-triggered. If it happens at launch, or at the same point however the UI is driven, on multiple images, it is the app. If it only happens on one image, suspect the image. The logcat stack trace and the step number separate these.

Q: What should I capture the moment a cloud phone shows “App keeps stopping”? A: In one atomic action: a screenshot of the dialog, a logcat excerpt around the FATAL EXCEPTION (including the exception type), the device and script timestamps, the script step number and action, and the image ID plus app package and version. Pull the logcat immediately — the buffer rolls over.

Q: Should my script retry after a crash, and how many times? A: Only for transient crashes on idempotent steps, and at most once: relaunch the app and resume from the last committed checkpoint. Retrying taps against a dead process is wasted motion and destroys the evidence; reproducible crashes should go to human review, not back into the loop.

Q: When should I reinstall the app on the device image instead of retrying? A: When the crash follows every launch, when clearing app data or reinstalling resets the failure, or when the installed version does not match the workload. If the crash survives a clean reinstall on a fresh image, escalate — the problem is the app itself.

Sources