惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Microsoft Azure Blog
Microsoft Azure Blog
aimingoo的专栏
aimingoo的专栏
Vercel News
Vercel News
U
Unit 42
L
LangChain Blog
J
Java Code Geeks
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
F
Fortinet All Blogs
小众软件
小众软件
I
InfoQ
P
Proofpoint News Feed
D
DataBreaches.Net
Martin Fowler
Martin Fowler
H
Help Net Security
T
Tailwind CSS Blog
N
Netflix TechBlog - Medium
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
B
Blog RSS Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
B
Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How to Debug LLM-Driven Android Automation Runs
Elliot Gao · 2026-05-26 · via DEV Community

Elliot Gao

LLM-driven Android automation fails in strange ways.

The model may tap the wrong label. The screen may change between observation and action. A keyboard may cover the button. A permission dialog may appear. The app may still be loading. The UI dump may expose two identical "Continue" buttons.

If all you saved is the final screenshot, debugging is painful.

You need a run trace.

Quick answer

For every Android agent step, save:

  • the compact UI dump
  • the screenshot when needed
  • the model's chosen action
  • the actual device command
  • the result or structured error
  • recent logs
  • the top package/activity

The minimum useful trace looks like this:

observe: tap Button "Continue" #continue 540,860
model:   tap "Continue"
action:  hs tap "Continue" --visible --unique
result:  ok
wait:    hs wait "Dashboard" --timeout 15s
result:  TIMEOUT

Enter fullscreen mode Exit fullscreen mode

That is much easier to debug than "the agent failed."

The failure modes

Android agent failures usually fall into a few buckets.

Failure What it means
NOT_FOUND The target label or selector was not visible
AMBIGUOUS More than one visible node matched
TIMEOUT The expected next state never appeared
SECURE_WINDOW Android blocked screenshots for the current window
Wrong action The model chose a bad label or command
Stale observation The UI changed after the model saw it

Good tooling should preserve which bucket happened.

If everything becomes "click failed", the agent cannot recover intelligently.

Save the UI dump before the action

The UI dump is the agent's view of the world.

Save it before each model decision:

hs ui > run/0007-ui.txt

Enter fullscreen mode Exit fullscreen mode

For LLM agents, a compact action table is usually better than full XML:

fill  EditText  "Email"     #email     540,540
fill  EditText  "Password"  #password  540,640  [password]
tap   Button    "Continue"  #continue  540,860

Enter fullscreen mode Exit fullscreen mode

When a model picks the wrong action, this file tells you whether the model had a reasonable choice.

Save screenshots selectively

Screenshots are valuable, but you do not need a full native PNG on every step.

For most agent debugging:

hs see --size 768 run/0007-screen.jpg

Enter fullscreen mode Exit fullscreen mode

Use screenshots when:

  • the UI dump has too little information
  • the app renders custom controls
  • visual layout matters
  • a failure needs human review

Use the text UI as the default. Use screenshots as evidence.

Record the model action separately

Do not only save the final command.

Save what the model actually emitted:

{
  "step": 7,
  "model_action": "tap \"Continue\"",
  "tool_call": ["hs", "tap", "Continue", "--visible", "--unique"],
  "reason": "The login form is filled and Continue is visible."
}

Enter fullscreen mode Exit fullscreen mode

This matters because the bug may be in translation:

  • The model chose the right label, but the tool call used the wrong selector.
  • The model chose a coordinate when a label was available.
  • The model ignored an ambiguity warning.

Keep the model layer and tool layer separate.

Prefer structured errors

Exit codes and error codes are better than stderr scraping.

Handsets has common exit codes:

0  ok
2  NOT_FOUND
3  TIMEOUT
4  AMBIGUOUS

Enter fullscreen mode Exit fullscreen mode

In JSON mode, preserve the structured error:

hs --json tap "Continue" --visible --unique

Enter fullscreen mode Exit fullscreen mode

Then your agent can decide:

  • NOT_FOUND: dump UI again or scroll
  • AMBIGUOUS: ask for a narrower selector
  • TIMEOUT: capture screenshot and logs
  • SECURE_WINDOW: continue without screenshot

Keep logs close to the failing step

Android logs are noisy. A small tail near the failure is usually enough:

hs logs --tail 200 > run/0007-logcat.txt

Enter fullscreen mode Exit fullscreen mode

Pair logs with the UI dump and screenshot from the same step. Otherwise you end up with artifacts that are technically present but hard to correlate.

A simple artifact layout

Use numbered files:

run/
  0001-ui.txt
  0001-action.json
  0001-result.json
  0002-ui.txt
  0002-screen.jpg
  0002-action.json
  0002-result.json
  0002-logcat.txt

Enter fullscreen mode Exit fullscreen mode

This is not fancy. That is the point.

Before building a dashboard, make the run inspectable with plain files.

Replay is the next step

Once you have traces, replay becomes possible.

The useful replay is not pixel-perfect video. It is a timeline:

Step 1: observed Sign in
Step 2: tapped Sign in
Step 3: filled Email
Step 4: filled Password
Step 5: tapped Continue
Step 6: timed out waiting for Dashboard

Enter fullscreen mode Exit fullscreen mode

For teams, this timeline becomes the product. It lets an engineer see whether the model, the tool, or the app caused the failure.

FAQ

Why are LLM Android agents hard to debug?

Because failures can come from the model, the app, the Android UI state, the automation tool, or timing. A final screenshot does not tell you which layer failed.

Should I save screenshots for every step?

Not always. Save compact UI dumps for every step. Add screenshots for visual states, failures, and custom-rendered screens.

What is the most important artifact?

The pre-action UI dump. It shows what the model saw when it chose the action.

How does this help reliability?

Structured traces let you build targeted recovery: scroll on NOT_FOUND, narrow selectors on AMBIGUOUS, capture logs on TIMEOUT, and avoid retrying blindly.

Related guides


Originally published at https://handsets.dev/blog/debug-llm-android-automation-runs/.