Skill v1.0.1
currentAutomated scan100/100+3 new
name: Debroid CLI Debugger description: "Orchestrate headless Android debugging via JDWP. ACTIVATE this skill whenever asked to debug an Android application, set line or exception breakpoints, inspect runtime variables or Jetpack Compose state, step through execution, evaluate live expressions, watch fields, or diagnose runtime crashes." metadata: version: "1.0.1"
Debroid CLI Debugger Skill
This skill provides mandatory instructions for AI agents to use the debroid CLI for headless Android debugging. Follow these instructions strictly to avoid context window bloat and execution errors.
🎯 Trigger Conditions
Activate this skill whenever the user or task involves:
- Debugging an Android application or process live
- Setting line breakpoints in Kotlin or Java source files (
debroid break) - Catching uncaught or caught runtime exceptions (
debroid catch-exception) - Inspecting live variables, object memory, or Jetpack Compose state (
debroid locals,pause-state,inspect) - Stepping through Kotlin/Java code execution (
debroid step) - Monitoring field access or modifications with watchpoints (
debroid watch) - Evaluating expressions or mutating variable values in runtime memory (
debroid eval,set-var)
⚠️ Critical Agent Rules (DO NOT IGNORE)
- Always use `--with-stacktrace` when polling to get immediate context.
- Never poll in an infinite loop. Poll once or twice, and if the app hasn't hit a breakpoint, instruct the user to interact with the app.
- Parse JSON outputs carefully. All CLI outputs are JSON strings. Extract
sessionId,threadId,objectId,breakpointId,exceptionBreakpointId,watchpointId, andcontinuationIdexactly as provided and reuse them verbatim in subsequent commands. Do not synthesize IDs. - Track trap IDs and remove them when done. Every
break,catch-exception, andwatchreturns an ID. Useremove-break,remove-catch-exception,remove-watchto clear traps you no longer need so they don't fire unexpectedly and waste poll cycles. - Keep IDs in working memory, not in long scratch buffers. A typical session uses 1–3 trap IDs plus 1 thread ID — keep them inline.
- Use Background Tasks: If you are using an agentic system that supports background tasks, run
debroid daemonas a background task. - Token efficiency & Schema Inspection: All JSON responses are compact single-line by default to conserve agent context tokens. If formatted multi-line JSON is required, append the
--prettyflag to any JSON command. To inspect the expected output JSON structure for any command without running a session, pass the--schemaflag (e.g.,debroid pause-state --schema). Do not echo full responses back to the user verbatim — summarize the relevant fields (valuePreview,type,objectId,location). - Self-Recovery: If a session becomes unresponsive or gets out of sync, run
debroid stopto cleanly terminate the daemon and release ADB ports. The nextdebroidcommand will auto-restart a fresh daemon. - Report, but don't block: If you hit unexpected Debroid behavior or have feedback on the tool itself, first try to resume the flow of what the user originally wanted to perform. Do not immediately jump to reporting the issue. At the end of your task, ask the user if they want to report the issue. See "🐛 Reporting Bugs & Feedback" below for details.
🔄 Standard Debugging Workflow
Step 1: Start the Daemon
Debroid requires a persistent background daemon to communicate with the Android device via JDWP.
debroid daemon &
(Wait 1-2 seconds for it to start). The first CLI command will also auto-start the daemon if it isn't running.
Step 2: Attach to the App
You must attach the debugger to obtain a sessionId.
- If the user wants to debug app startup:
``bash debroid launch <app_id> debroid break <session_id> <FileName.kt> <line_number> --package <pkg> debroid resume <session_id> ` > ⚠️ launch starts the app suspended via am set-debug-app -w and halts the VM with all threads suspended (suspendedThreadsCount > 0). Set your startup breakpoints or watchpoints immediately after launch, and then call debroid resume <session_id> so the VM proceeds with startup and hits your breakpoints. (Pass --no-suspend if you want the app to start executing immediately upon launch without waiting). > ⚠️ launch uses am set-debug-app -w. Debroid **automatically** clears this flag when you call detach, so the next normal (non-debug) launch of the app will not hang. Do not call am clear-debug-app` yourself.
- If the app is already running:
``bash debroid attach <app_id> ` *(Pass --suspend if you want to suspend all threads immediately upon attaching).* **Action:** Extract the sessionId` from the JSON response.
⚠️ Session Lifecycle & Breakpoints (Crucial!):Breakpoints, watchpoints, and exception traps are strictly session-scoped. When you callattachorlaunchfor anapp_idthat is already being debugged by an older/stale session, Debroid's automatic deduping will instantly safely detach that stale session. This completely wipes all breakpoints and watchpoints you may have set previously. You MUST recreate any breakpoints or watchpoints you need using your newsessionIdreturned by the newattach/launchcommand!
Step 3: Set Traps
Set your breakpoints or watchpoints before polling. Save the returned IDs.
- Line Breakpoint:
debroid break <session_id> <FileName.kt> <line_number> [--package <pkg>] [--pretty]→ returns{ "id": "bp_1", "verified": true|false, ... } - Always pass `--package` when you know it (e.g.
--package com.example.app.search). Providing the package name lets the daemon resolve the class with a single targeted lookup instead of scanning every loaded class across the entire VM, which takes seconds or minutes on large applications and drastically improves responsiveness. - If
verified=false, the class isn't loaded yet. Debroid will automatically bind it the moment the class is prepared — you do not need to do anything; just poll. (No more deferred-breakpoint dead-ends.) - Use
remove-break <session_id> <breakpoint_id>to clear it. - Exception Trap:
debroid catch-exception <session_id> [ExceptionClass] [--caught] [--uncaught] - Defaults:
--uncaughtON,--caughtOFF. Add--caughtto also trap exceptions caught by the app. - Returns
{ "exceptionBreakpointId": "ex_bp_1" }. - Remove with
remove-catch-exception <session_id> <exception_breakpoint_id>. - Watchpoint:
debroid watch <session_id> <com.pkg.Class> <fieldName> [--access] [--modify] - Defaults: BOTH access and modify ON. Pass
--no-accessor--no-modifyto disable either. - Returns
{ "watchpointId": "wp_1" }(or anINVALID_ARGUMENTerror if the target class is already loaded and<fieldName>does not exist on it). - Remove with
remove-watch <session_id> <watchpoint_id>.
Step 4: Poll for Events
You must poll the event queue to see if a trap was triggered.
debroid poll <session_id> <cursor> --with-stacktrace
- Initial Cursor: Always start with
"0". - Subsequent Cursors: Use the
nextCursorvalue from the previous JSON response. - Buffer Overflow & Stale Cursors: Inspect
droppedEventsSinceLastPoll(omitted from JSON when 0). If present and > 0, events were evicted from the buffer before they could be polled (e.g. due to high event volume or delayed polling). If an expected breakpoint or exception event is not present inevents, it may have been among the dropped events. - Waiting: If
eventsis empty, ask the user to trigger the action in the app, then poll again. - Event types you may see:
BREAKPOINT_HIT(includesbreakpointId),STEP_HIT,EXCEPTION_HIT(exceptionMessageincludes<ExceptionClass>: <detailMessage>),WATCHPOINT_ACCESS_HIT(includesstacktracewhen--with-stacktraceis set),WATCHPOINT_MODIFY_HIT(includesstacktracewhen--with-stacktraceis set),DISCONNECT.
Step 5: Inspect the Pause State
Once poll returns an event (e.g., BREAKPOINT_HIT), extract the threadId. Use the Pause State Command to get frames and locals at once:
debroid pause-state <session_id> <thread_id>
Advanced Inspections:
- Drill into complex objects or arrays:
debroid inspect <session_id> <object_id> [OPTIONS] --max-depth Nrecurses N levels deep into object fields and array elements. Responses include anestedmap keyed by field name (or array index"[0]","[1]", ...). Cycles are guarded automatically.- Array Inspection: Inspecting an array
objectId(or a collection's backing array) populatesfields(andnestedwhen--max-depth > 1) with indexed keys ("[0]","[1]", ...). - Important Filter Behavior: By default,
inspectexplicitly filters outstaticfields,syntheticfields, and ART-specific internal fields (shadow$_*). It also prevents redundant recursion into well-known immutable terminal types (likejava.lang.Stringand Enums) to save your context tokens. --include-staticand--include-internalcan be passed to bypass these filters if you absolutely need to inspect Dalvik internals or static class constants.- Coroutine state:
debroid coroutine <session_id> <continuation_id>
ObjectId Lifecycle & Reuse (Crucial!):AnyobjectIdorcontinuation_idreturned by ANY command (includinglocals,pause-state,pollstacktraces,eval, or even inside a deeply nestedinspectresult) is globally cached by the daemon and guaranteed to remain valid and reusable across multiple standalone commands for the entire duration of the current suspend. You can confidently extract nestedobjectIds from a shallow inspect and drill into them later. These IDs are only invalidated/cleared when the VM is explicitly resumed (e.g. viastep,resume) or detached.
Step 6: Mutate or Step
- Mutate memory:
debroid set-var <session_id> <thread_id> <variableName> <newValue> - Important:
newValueis parsed as a Java expression! To set a String literal, you must wrap the value in escaped quotes (e.g.,\"my string\"). To set it to the result of an expression, provide the expression (e.g.,5,true,varName + 1, Int/Long:10,100L, Float/Double:10.5f,20.5d). - Evaluate expressions:
debroid eval <session_id> <thread_id> "<expression>" - Expression Engine: Supports compound boolean/logical operators (
&&,||,!), relational comparisons (<,<=,>,>=,==,!=), arithmetic, and idiomatic Kotlin/Java expressions. - Supported Capabilities:
- Compound Logic & Relational: Evaluate expressions directly (e.g.,
amount >= 600.0 && isExpress,(x > 5 || y == 0) && !flag). - Kotlin Properties & Getters: Access Kotlin properties directly (e.g.,
order.amountoruser.name) — automatically resolves backing fields or getter methods (getAmount(),isExpress()). - Safe Calls & Elvis: Supports safe navigation and fallback defaults (e.g.,
user?.address?.city,user?.name ?: "Unknown"). - Type Casting & Checks: Supports safe and unsafe type casts and type checks (e.g.,
(user as Admin).permissions,user as? Admin ?: defaultAdmin,order is Order,42 as Long). - Method Calls & Arithmetic: Supports method invocations, parameter passing, and math operators (e.g.,
order.getAmount() * 0.15,processor.calculateTotal(order)). - Static Methods, Constants & Singletons: Supports calling static methods and accessing static constants. Encouraged Best Practice: Prefer fully-qualified class names (e.g.,
java.lang.Math.max(10, 20),android.util.Log.d("Tag", "msg"),java.lang.System.currentTimeMillis(),java.lang.Integer.MAX_VALUE,com.example.OrderConfig.currency,com.example.OrderValidator.isValid(50.0)). Standard default imports (java.lang.*,java.util.*,kotlin.*,kotlin.math.*,android.util.*, etc.) and classes in the active stack frame's package are also resolved automatically when unqualified. - Kotlin Objects & Companion Functions: Supports standalone Kotlin
objectsingletons (e.g.,OrderConfig.formatCurrency(100.0)), companion object properties/methods (OrderValidator.MIN_AMOUNT), and@JvmStaticmethods. - Field Inspection: To view an object's instance fields in detail, retrieve its
objectIdfromlocalsorpause-stateand usedebroid inspect <session_id> <object_id>. - 🚫 Unsupported & Impossible in `eval` (DO NOT ATTEMPT IN SESSIONS):
- Kotlin Extension Functions syntax (`receiver.extFunc()`): Extension functions are compiled to static methods in file facade classes (e.g.,
StringExtKt.extFunc(receiver)). They are NOT instance methods on the receiver. To call an extension function, invoke it explicitly via its fully-qualified static facade class:com.example.utils.StringExtKt.extFunc(receiver). - Lambdas & Higher-Order Functions: Passing inline closures/lambdas (e.g.,
list.map { it.name },items.filter { it.price > 10 },list.forEach { ... }) is NOT supported by the runtime JDI evaluator (cannot synthesize anonymous classes at runtime). - Control Flow Statements:
if-elsestatements,whenblocks, loops (for,while,do-while),try-catch,return,throw, etc. are not supported. Use elvis?:or boolean logic expressions instead. - Variable Declarations & Assignments: Declaring new variables (
val x = 10) or direct assignment statements (x = 20) inevalis not supported. Usedebroid set-var <session_id> <thread_id> <var_name> <new_value>to mutate existing variables. - Object Instantiation via `new` / Constructor calls: Creating new object instances via constructors (e.g.,
User("Alice"),new HashMap()) is not supported. - Coroutines & `suspend` Functions: Calling
suspendfunctions directly insideevalis not supported. Inspect active continuation frames usingdebroid coroutine <session_id> <continuation_id>. - Direct Access to Private Fields without Getters: To inspect private fields without public accessors, use
debroid inspect <session_id> <object_id>instead ofeval. - Step execution:
debroid step <session_id> <thread_id> <ACTION>(Actions:STEP_OVER,STEP_INTO,STEP_OUT,RESUME_THREAD,RESUME_ALL) - Resume all threads:
debroid resume <session_id>
> Note: resume resumes all threads in the VM. For per-thread resume, use step <session_id> <thread_id> RESUME_THREAD.
Step 7: Clean Up
When debugging is complete, cleanly detach:
debroid detach <session_id>
If the session was opened withlaunch,detachautomatically runsadb shell am clear-debug-appso the user's app resumes normal behavior. You do not need to clear it manually.
🚨 Common Pitfalls & Error Recovery
- Error: "Failed to communicate with daemon": The daemon isn't running. Run
debroid daemon &. - Error: "AbsentInformationException": The app is not debuggable or was obfuscated by ProGuard. Ensure
android:debuggable="true"in the Manifest. - Missing Local Variables: If locals are empty, you might be at a method entry point. Run a
STEP_OVERand check again. - Inspecting Complex Objects: If an object does not expose public properties or methods, retrieve its
objectIdfromlocalsorpause-stateand usedebroid inspect <session_id> <object_id>to view all private fields recursively. - `break` returns `verified=false`: NOT a failure — the class will be loaded later and the breakpoint will be bound automatically. Keep your
bpid and proceed. - A trap no longer needed: Remove it. Lingering exception traps in particular can fire on every exception the app throws, flooding your poll results.
- Jetpack Compose Breakpoints: Place line breakpoints on executable statements inside
@Composablefunction bodies (e.g.val x = ...), not on the@Composable fun Name(...)signature header line where no bytecode is generated. - Jetpack Compose State Delegates: Compose
remember { mutableStateOf(...) }orcollectAsState()variables appear as<varName>$delegateinpause-stateorlocals. To inspect Compose state values, retrieve theobjectIdof the$delegateobject and usedebroid inspect <session_id> <object_id>. - Autonomous UI Interaction: Instead of asking the user to click buttons on the device, inspect the UI bounds using
android layout --pretty(preferandroid-cliif installed) oradb shell uiautomator dump /sdcard/window_dump.xml && adb shell cat /sdcard/window_dump.xml, locate the target element'sbounds="[minX,minY][maxX,maxY]", calculate the center coordinates(minX + maxX)/2, and simulate a tap withadb shell input tap X Y. - `detached` of `launch`'d session and app won't start normally: Should not happen —
detachclearsam set-debug-app. If it does (e.g. daemon was forcibly killed), runadb shell am clear-debug-apponce. - Daemon or Session Unresponsive: Run
debroid stopto terminate the background server and clear ADB port forwards. The nextdebroidcommand will automatically spawn a fresh, clean daemon. - Daemon Startup Failures: If the auto-spawned daemon fails to start, review the surfaced startup diagnostics in the error output or inspect
~/.debroid/daemon.logfor full details.
🐛 Reporting Bugs & Feedback (Debroid Tool Itself)
If you hit unexpected Debroid behavior (a crash, a wrong/malformed result, a stuck session debroid stop doesn't fix) — not a bug in the app you're debugging — or have feedback on the tool itself, do not immediately jump to reporting the issue.
- First, try to resume the flow of what the user originally wanted to perform or find a workaround to complete their task.
- At the very end of your response or task completion, ask the user: "Would you like me to report the issue we encountered with Debroid?"
- Only if the user explicitly says yes, proceed to fetch the full reporting workflow (sanitizing details, exact report format, mandatory user confirmation, filing via
gh):
curl -fsSL https://raw.githubusercontent.com/PatilShreyas/debroid/main/skills/debroid-cli/references/reporting-feedback.md
Then follow it exactly.
📊 Token-Efficient JSON Extraction & Querying Guidelines
To keep context windows clean and minimize token consumption during debugging sessions:
- Direct Output Reading (Default for Small/Medium Commands):
- For
attach,launch,break,locals,pause-state,set-var,eval: Readstdoutdirectly into context memory. Single-line compact responses take only ~50-80 tokens.
- Filtering Large Payloads via `jq` / Python (When Payload is Large):
- For deep
inspectqueries (-d 2+) or long eventpollresponses with stacktraces, pass--schemaupfront to know key names, then pipe output throughjqin the shell to extract target fields before reading into context:
```bash # Filter specific local variable from pause-state: debroid pause-state <session_id> <thread_id> | jq '.locals[] | select(.name=="targetVar")'
# Extract specific field during deep object inspection: debroid inspect <session_id> <object_id> -d 2 | jq '.fields.order'
# Extract event types and exception messages from poll: debroid poll <session_id> <cursor> --with-stacktrace | jq '.events[] | {eventType, className, exceptionMessage}' ```
- Direct VM Expression Querying (`eval`):
- To query a single calculated primitive value or method result (e.g.
order.getAmount()orlist.size()), calldebroid eval <session_id> <thread_id> "<expr>". This evaluates the expression directly in the Dalvik/ART VM and returns a tiny single-value payload (< 20 tokens).
📖 Complete CLI Command Reference
Here is the full list of commands and their signatures (note: all commands support global options -p, --port <int> / DEBROID_PORT environment variable, --version, and -h, --help; all JSON commands accept an optional --pretty flag to format response JSON, and an eager --schema flag to inspect output types):
| Command | Signature | Description | |
|---|---|---|---|
daemon | debroid daemon | Starts the persistent background daemon | |
stop | debroid stop [--pretty] | Shuts down background daemon and detaches all active sessions | |
launch | debroid launch <app_id> [--suspend/--no-suspend] [--pretty] | Launches app suspended and attaches (auto-clears set-debug-app on detach) | |
attach | debroid attach <app_id> [--suspend] [--pretty] | Attaches to a running app | |
detach | debroid detach <session_id> [--pretty] | Safely detaches debugger; for launch sessions also clears am set-debug-app | |
break | debroid break <session_id> <file> <line> [-p/--package=<pkg>] [--pretty] | Sets a line breakpoint (always pass --package for fast targeted lookup; auto-defers if class isn't loaded yet) | |
remove-break | debroid remove-break <session_id> <breakpoint_id> [--pretty] | Removes a previously set line breakpoint | |
catch-exception | debroid catch-exception <session_id> [class_name] [--caught] [--uncaught] [--pretty] | Sets an exception breakpoint (default: --uncaught only) | |
remove-catch-exception | debroid remove-catch-exception <session_id> <exception_breakpoint_id> [--pretty] | Removes an exception breakpoint | |
watch | debroid watch <session_id> <class_name> <field_name> [--access] [--modify] [--pretty] | Sets a field watchpoint (default: access+modify) | |
remove-watch | debroid remove-watch <session_id> <watchpoint_id> [--pretty] | Removes a watchpoint | |
points | debroid points <session_id> [--pretty] | Retrieves all active debug points (breakpoints, exception points, watchpoints) for a session | |
threads | debroid threads <session_id> [--pretty] | Lists active threads | |
locals | debroid locals <session_id> <thread_id> [--pretty] | Gets shallow local variables and method parameters | |
pause-state | debroid pause-state <session_id> <thread_id> [--pretty] | Gets frames, locals (including method parameters), and instance state | |
set-var | debroid set-var <session_id> <thread_id> <var_name> <new_value> [--pretty] | Mutates local variable. <new_value> is parsed as a Java expression (e.g. 10, 100L, 10.5f, 20.5d, true, "\"my string\""). Ensure quotes are escaped in shell. | |
eval | debroid eval <session_id> <thread_id> <expression...> [--pretty] | Evaluates string expression | |
resume | debroid resume <session_id> [--pretty] | Resumes all threads (use step ... RESUME_THREAD for per-thread resume) | |
poll | debroid poll <session_id> [cursor=0] [--with-stacktrace] [--pretty] | Polls for asynchronous debugger events | |
frames | debroid frames <session_id> <thread_id> [--pretty] | Retrieves thread stack frames | |
coroutine | debroid coroutine <session_id> <continuation_id> [--pretty] | Retrieves locals from a Continuation object | |
inspect | debroid inspect <session_id> <object_id> [-d/--max-depth=<int>] [--include-static] [--include-internal] [--pretty] | Inspects deep object fields (nested map populated when --max-depth > 1; excludes static and $ internal fields by default) | |
step | debroid step <session_id> <thread_id> <action> [--pretty] | Steps execution (STEP_OVER, STEP_INTO, STEP_OUT, RESUME_THREAD, RESUME_ALL) | |
update | debroid update [--check-only] [--pretty] | Checks for CLI updates or performs an in-place self-update to the latest release |