<< All versions

Skill v1.0.1

currentAutomated scan100/100
patilshreyas/debroid/debroid-cli

+3 new

──Details
PublishedNovember 1, 2026 at 02:32 AM
Content Hashsha256:aed3f9c213d9427f...
Git SHAe5055d8e84df
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 23.3 KB)
SKILL.md23.3 KBactive
SKILL.md · 216 lines · 23.3 KB

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)

  1. Always use `--with-stacktrace` when polling to get immediate context.
  2. 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.
  3. Parse JSON outputs carefully. All CLI outputs are JSON strings. Extract sessionId, threadId, objectId, breakpointId, exceptionBreakpointId, watchpointId, and continuationId exactly as provided and reuse them verbatim in subsequent commands. Do not synthesize IDs.
  4. Track trap IDs and remove them when done. Every break, catch-exception, and watch returns an ID. Use remove-break, remove-catch-exception, remove-watch to clear traps you no longer need so they don't fire unexpectedly and waste poll cycles.
  5. 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.
  6. Use Background Tasks: If you are using an agentic system that supports background tasks, run debroid daemon as a background task.
  7. 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 --pretty flag to any JSON command. To inspect the expected output JSON structure for any command without running a session, pass the --schema flag (e.g., debroid pause-state --schema). Do not echo full responses back to the user verbatim — summarize the relevant fields (valuePreview, type, objectId, location).
  8. Self-Recovery: If a session becomes unresponsive or gets out of sync, run debroid stop to cleanly terminate the daemon and release ADB ports. The next debroid command will auto-restart a fresh daemon.
  9. 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.

bash
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 call attach or launch for an app_id that 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 new sessionId returned by the new attach/launch command!

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: --uncaught ON, --caught OFF. Add --caught to 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-access or --no-modify to disable either.
  • Returns { "watchpointId": "wp_1" } (or an INVALID_ARGUMENT error 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.

bash
debroid poll <session_id> <cursor> --with-stacktrace
  • Initial Cursor: Always start with "0".
  • Subsequent Cursors: Use the nextCursor value 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 in events, it may have been among the dropped events.
  • Waiting: If events is empty, ask the user to trigger the action in the app, then poll again.
  • Event types you may see: BREAKPOINT_HIT (includes breakpointId), STEP_HIT, EXCEPTION_HIT (exceptionMessage includes <ExceptionClass>: <detailMessage>), WATCHPOINT_ACCESS_HIT (includes stacktrace when --with-stacktrace is set), WATCHPOINT_MODIFY_HIT (includes stacktrace when --with-stacktrace is 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:

bash
debroid pause-state <session_id> <thread_id>

Advanced Inspections:

  • Drill into complex objects or arrays: debroid inspect <session_id> <object_id> [OPTIONS]
  • --max-depth N recurses N levels deep into object fields and array elements. Responses include a nested map 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) populates fields (and nested when --max-depth > 1) with indexed keys ("[0]", "[1]", ...).
  • Important Filter Behavior: By default, inspect explicitly filters out static fields, synthetic fields, and ART-specific internal fields (shadow$_*). It also prevents redundant recursion into well-known immutable terminal types (like java.lang.String and Enums) to save your context tokens.
  • --include-static and --include-internal can 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!):
Any objectId or continuation_id returned by ANY command (including locals, pause-state, poll stacktraces, eval, or even inside a deeply nested inspect result) 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 nested objectIds from a shallow inspect and drill into them later. These IDs are only invalidated/cleared when the VM is explicitly resumed (e.g. via step, resume) or detached.

Step 6: Mutate or Step

  • Mutate memory: debroid set-var <session_id> <thread_id> <variableName> <newValue>
  • Important: newValue is 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.amount or user.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 object singletons (e.g., OrderConfig.formatCurrency(100.0)), companion object properties/methods (OrderValidator.MIN_AMOUNT), and @JvmStatic methods.
  • Field Inspection: To view an object's instance fields in detail, retrieve its objectId from locals or pause-state and use debroid 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-else statements, when blocks, 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) in eval is not supported. Use debroid 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 suspend functions directly inside eval is not supported. Inspect active continuation frames using debroid 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 of eval.
  • 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:

bash
debroid detach <session_id>
If the session was opened with launch, detach automatically runs adb shell am clear-debug-app so 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_OVER and check again.
  • Inspecting Complex Objects: If an object does not expose public properties or methods, retrieve its objectId from locals or pause-state and use debroid 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 bp id 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 @Composable function 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(...) } or collectAsState() variables appear as <varName>$delegate in pause-state or locals. To inspect Compose state values, retrieve the objectId of the $delegate object and use debroid 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 (prefer android-cli if installed) or adb shell uiautomator dump /sdcard/window_dump.xml && adb shell cat /sdcard/window_dump.xml, locate the target element's bounds="[minX,minY][maxX,maxY]", calculate the center coordinates (minX + maxX)/2, and simulate a tap with adb shell input tap X Y.
  • `detached` of `launch`'d session and app won't start normally: Should not happen — detach clears am set-debug-app. If it does (e.g. daemon was forcibly killed), run adb shell am clear-debug-app once.
  • Daemon or Session Unresponsive: Run debroid stop to terminate the background server and clear ADB port forwards. The next debroid command 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.log for 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.

  1. First, try to resume the flow of what the user originally wanted to perform or find a workaround to complete their task.
  2. 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?"
  3. 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):
bash
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:

  1. Direct Output Reading (Default for Small/Medium Commands):
  • For attach, launch, break, locals, pause-state, set-var, eval: Read stdout directly into context memory. Single-line compact responses take only ~50-80 tokens.
  1. Filtering Large Payloads via `jq` / Python (When Payload is Large):
  • For deep inspect queries (-d 2+) or long event poll responses with stacktraces, pass --schema upfront to know key names, then pipe output through jq in 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}' ```

  1. Direct VM Expression Querying (`eval`):
  • To query a single calculated primitive value or method result (e.g. order.getAmount() or list.size()), call debroid 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):

CommandSignatureDescription
daemondebroid daemonStarts the persistent background daemon
stopdebroid stop [--pretty]Shuts down background daemon and detaches all active sessions
launchdebroid launch <app_id> [--suspend/--no-suspend] [--pretty]Launches app suspended and attaches (auto-clears set-debug-app on detach)
attachdebroid attach <app_id> [--suspend] [--pretty]Attaches to a running app
detachdebroid detach <session_id> [--pretty]Safely detaches debugger; for launch sessions also clears am set-debug-app
breakdebroid 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-breakdebroid remove-break <session_id> <breakpoint_id> [--pretty]Removes a previously set line breakpoint
catch-exceptiondebroid catch-exception <session_id> [class_name] [--caught] [--uncaught] [--pretty]Sets an exception breakpoint (default: --uncaught only)
remove-catch-exceptiondebroid remove-catch-exception <session_id> <exception_breakpoint_id> [--pretty]Removes an exception breakpoint
watchdebroid watch <session_id> <class_name> <field_name> [--access] [--modify] [--pretty]Sets a field watchpoint (default: access+modify)
remove-watchdebroid remove-watch <session_id> <watchpoint_id> [--pretty]Removes a watchpoint
pointsdebroid points <session_id> [--pretty]Retrieves all active debug points (breakpoints, exception points, watchpoints) for a session
threadsdebroid threads <session_id> [--pretty]Lists active threads
localsdebroid locals <session_id> <thread_id> [--pretty]Gets shallow local variables and method parameters
pause-statedebroid pause-state <session_id> <thread_id> [--pretty]Gets frames, locals (including method parameters), and instance state
set-vardebroid 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.
evaldebroid eval <session_id> <thread_id> <expression...> [--pretty]Evaluates string expression
resumedebroid resume <session_id> [--pretty]Resumes all threads (use step ... RESUME_THREAD for per-thread resume)
polldebroid poll <session_id> [cursor=0] [--with-stacktrace] [--pretty]Polls for asynchronous debugger events
framesdebroid frames <session_id> <thread_id> [--pretty]Retrieves thread stack frames
coroutinedebroid coroutine <session_id> <continuation_id> [--pretty]Retrieves locals from a Continuation object
inspectdebroid 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)
stepdebroid step <session_id> <thread_id> <action> [--pretty]Steps execution (STEP_OVER, STEP_INTO, STEP_OUT, RESUME_THREAD, RESUME_ALL)
updatedebroid update [--check-only] [--pretty]Checks for CLI updates or performs an in-place self-update to the latest release
← v1.0.0All versions