MO

mobile-dev-debug-tool

Provides access to live client state, logs, and GDScript evaluation via a WebSocket debug-hub.

Install

mkdir -p .claude/skills/mobile-dev-debug-tool && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14430" && unzip -o skill.zip -d .claude/skills/mobile-dev-debug-tool && rm skill.zip

Installs to .claude/skills/mobile-dev-debug-tool

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Use when inspecting or reporting the state of the running Decentraland Godot Explorer client — on desktop or on a mobile device (iOS/Android) — over the unified scene-inspector debug-hub (`cargo run -- debug-hub`; device port 9231, consumer port 9230) that the client dials out to. Covers the SCENE_INSPECTOR_CMD JSON protocol, the five trees (`scene`/`entity`, `ui_scene`/`ui_entity`, `avatars`/`avatar`, `app_ui`, `ping`/`scenes`), the `focus` keyboard-focus tracker, the `log`/`network` streams, the shared `filters` dict, the `websocat` helper scripts (`scripts/unified.sh`, `unified-tail.sh`), and the wiring across `godot/src/tool/debug_server/`, `scene_inspector_bridge.gd` and the Rust `SceneManager::debug_*` / `AvatarScene::debug_*` hooks. Also covers the `eval` command for running arbitrary GDScript against the live client (non-production only). Trigger when the user asks what state the running app/client is in (what scenes/realm are loaded, where the avatar is, what the UI is showing — desktop or on-device/mobile/iOS/Android), asks to connect to or host the debug-hub, or mentions the scene-inspector channel, debug-hub, port 9230/9231, `DebugWs`, `debug_collector`, websocat against the client, or running/evaluating GDScript against the running client.
1272 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Inspect live client state including scenes, entities, UI, and avatars
  • Monitor log, network, and lifecycle streams
  • Execute arbitrary GDScript against the live client
  • Connect to a debug-hub on desktop or mobile devices
  • Tail specific log messages for feature instrumentation

How it works

The client dials out to a desktop debug-hub via a WebSocket channel, exposing live state and streams. Local tools connect to the hub's consumer port to interact with the client.

Inputs & outputs

You give it
Running Decentraland Godot Explorer client connected to a debug-hub
You get back
Live client state data (scenes, entities, UI, avatars), log streams, and results from GDScript evaluation

When to use mobile-dev-debug-tool

  • Inspecting avatar state in a scene
  • Monitoring network logs during runtime
  • Executing GDScript for live client debugging

About this skill

Mobile dev debug tool — scene-inspector debug-hub

A single WebSocket channel exposes live client state (scenes / entities / UI / avatars / focus), a log/network/lifecycle stream, and an eval command that runs arbitrary GDScript — reachable on any platform, including iOS/Android devices that can't be dialed into. The client dials out to a desktop debug-hub; local tools (AI / websocat) connect to the hub's consumer port.

There is one transport: the scene-inspector CMD protocol (the source-of-truth contract an external inspector app already parses, so additions stay backward-compatible). eval is hard-disabled in production builds.

Bring up the hub

cargo run -- debug-hub                       # device port 9231, consumer port 9230
# launch the client pointed at the hub's device port (LAN IP shown in the banner):
cargo run -- run -- --scene-inspector=ws://127.0.0.1:9231              # desktop
cargo run -- run --target ios -- --scene-inspector=ws://<this-mac>:9231   # device

On iOS the dcl-ios-devtools export plugin auto-bakes the hub address (debug builds), so even a Godot-editor deploy phones home — just accept the local-network prompt on first launch. The bridge activates at boot (global.gd::_activate_scene_inspector_from_config, from _ready + on every deeplink), so the channel is live from the lobby, before login — no need to enter a world first.

Answering "what state is the app in?" — ONE step

Run the pre-armed connector in the background, read its output, then query:

scripts/hub-connect.sh          # Bash tool: run_in_background: true — read its output

hub-connect.sh does the whole cold-start dance in one shot: wires Android adb reverse, ensures a hub (reuse or start), waits ≤35 s for the device to dial in, and prints either === CONNECTED === + a ping snapshot or a NO DEVICE relaunch hint. If it started the hub it keeps the task alive so the hub persists. Then query with the id-filtered helpers:

scripts/unified.sh scenes
scripts/unified.sh scene '{"scene_id":0,"filters":{"limit":5}}'
scripts/unified.sh eval  'return {"scene": str(get_tree().current_scene.name), "scenes_loaded": Global.scene_runner.debug_get_loaded_scene_ids().size()}'

Why it just works: the client dials out and retries forever (backoff 1s→…→30 s cap, scene_inspector_websocket.gd), and debug builds default the target to ws://127.0.0.1:9231 with no arg (global.gd), so a hub started after the app is picked up within ≤30 s — no app restart needed. Per platform:

  • iOS — the dcl-ios-devtools export plugin bakes ws://<LAN-IP>:9231 (even a Godot-editor deploy phones home); accept the Local Network prompt once.
  • Android — NO plugin bakes the arg (the baking plugin is iOS-only, and Android editor-deploy CLI args don't reach the app). The debug loopback default + adb reverse tcp:9231 (set by hub-connect.sh) carry it instead. A build made before that default won't connect — rebuild+redeploy, or use cargo run -- run --target android.
  • desktopcargo run -- run -- --scene-inspector=ws://127.0.0.1:9231, or an editor F5 also auto-dials loopback.

If hub-connect.sh reports NO DEVICE, the app simply isn't dialing — follow the hint it prints (usually: relaunch/redeploy the app).

Wiring

  • Command backend: DebugWs autoload → godot/src/tool/debug_server/debug_ws_server.gd (run_command) + debug_collector.gd (data assembly). No longer a server — purely the shared inspection/eval backend + keyboard-focus tracker.
  • Transport: godot/src/tool/scene_inspector_bridge.gd (drives CMD ↔ ACK and the streams) + godot/src/logic/scene_inspector_websocket.gd; Rust side in lib/src/tools/scene_inspector/. The hub is the debug-hub xtask (src/log_server.rs).
  • Rust #[func] hooks for state only Rust can reach:
    • SceneManager::debug_* (lib/src/scene_runner/scene_manager.rs) — CRDT enumeration, deserialization, UI control lookup.
    • AvatarScene::debug_* (lib/src/avatars/avatar_scene.rs) — avatar listing, address/alias/entity/local lookup.

Protocol (scene-inspector CMD)

  • request: {"type":"SCENE_INSPECTOR_CMD","cmd":"<verb>","args":{...},"id":"<id>"}
  • reply: {"type":"SCENE_INSPECTOR_CMD_ACK","id":"<id>","ok":<bool>,"data":...} (or {"ok":false,"error":"..."})
  • streams (push): {"type":"SCENE_INSPECTOR","payload":{"sessionId":...,"entries":[{type:...}]}} where entries[].type ∈ crdt | op_call_start | op_call_end | scene_lifecycle | perf | log | network | session_start | session_end.

The id is echoed in the ACK — always match replies by it (see the perf-vs-ACK note below). The helpers (scripts/unified.sh) do this for you.

Five trees, one command surface

cmdTreeIdentified by
scene / entity3D entity tree (DclSceneNodeDclNodeEntity3d)(scene_id, entity_id)
ui_scene / ui_entityper-scene SDK UI (UiNode.base_control)(scene_id, entity_id)
avatars / avatarglobal AvatarSceneby ∈ {address,alias,entity,local}
app_uiExplorer's own UIauto-detected (/root/explorer/UI or /root/Menu)
ping / scenes / focus

All four data cmds (scene, ui_scene, avatar, app_ui) share a filters dict:

  • component: [...] — OR-match SDK component names (cheap, no proto decode)
  • property_is: {component, field, contains} — generic substring filter on any (SDK component, field) pair
  • collect_nodes: {<child_name>: [<property>, ...]} — per-child-node property dump via Object.get(); values pass through _variant_to_json
  • include_parents, include_children, limit, offset, depth, class_filter, name_contains — traversal/pagination knobs

Querying — unified.sh

scripts/unified.sh <cmd> [args-json] sends one CMD frame to the hub consumer port (ws://127.0.0.1:9230) and returns its matching ACK. It bakes in -B 16777216 so websocat doesn't split large replies, and keeps the socket open until the ACK's id matches (needed for on-device round-trips). Requires websocat on $PATH (cargo install websocat or your package manager).

# Confirm the round-trip to the connected client
scripts/unified.sh ping

# All loaded scenes
scripts/unified.sh scenes

# All TextShape entities in scene 0 with their Label3D properties
scripts/unified.sh scene '{"scene_id":0,"filters":{
  "component":["TextShape"],
  "collect_nodes":{"TextShape":["text","font_size","pixel_size","outline_size","modulate"]}
}}'

# Your own avatar — what it's wearing + what's playing
scripts/unified.sh avatar '{"by":"local","filters":{
  "collect_nodes":{"AnimationPlayer":["current_animation","autoplay"],"AnimationTree":["active"]}
}}'

# Explorer's own UI hierarchy (lobby in this state, scene UI when loaded)
scripts/unified.sh app_ui '{"filters":{"depth":2}}'

# Keyboard-focus tracker — current owner + ui_root + change history
scripts/unified.sh focus

Eval — running GDScript

scripts/unified.sh eval '<gdscript>' compiles and runs the snippet against the live client and returns the serialized result — the agent-facing equivalent of a devtools console. Non-production only: in a production build the cmd replies {"ok":false,"error":"eval disabled in production builds"}.

code is a GDScript function body. Use return X to send a value back. Three locals are in scope:

localwhat
treethe SceneTree (tree.root, tree.get_node(...))
globalthe Global autoload
serverthe DebugWsServer command-backend node

Autoloads (Global, DebugWs, …) and engine singletons (OS, Engine, Time, …) are reachable directly too. A bare single-line expression is auto-wrapped in return, so eval '1 + 1' works without the keyword.

The result passes through the same _variant_to_json used elsewhere (primitives, Vector*, Color, AABB, Array, Dictionary; everything else — Object/Node/Callable — falls back to str()).

# Bare expression (auto-wrapped)
scripts/unified.sh eval 'Engine.get_frames_per_second()'

# Reach into the tree
scripts/unified.sh eval 'return tree.get_root().get_child_count()'

# Multi-line statement body
scripts/unified.sh eval 'var names = []
for c in tree.get_root().get_children():
	names.append(c.name)
return names'

Limitations: synchronous onlyawait is not supported (it would return a coroutine signal, not the awaited value). GDScript runtime errors (e.g. a null access) are logged to the client console and the eval returns null with ok:true; only compile errors come back as ok:false.

focus — keyboard-focus tracker

focus takes no args and returns the viewport's current keyboard-focus owner plus a timestamped change history (no filters). Reply data: {"current": "<path> [<class>]", "ui_root_path": "/root/explorer/UI", "history": [{"t_ms", "frame", "from", "to"}, ...]} (last FOCUS_HISTORY_MAX = 64 changes; "<none>" means focus was released to null).

DebugWs polls get_viewport().gui_get_focus_owner() every _process frame (in debug builds) so the history captures transient changes — including release-to-null, which the engine's gui_focus_changed signal misses.

Use it for "input stops working" bugs: mobile walk/jump are gated by player.gdexplorer_has_focus() (== ui_root.has_focus()), so movement silently dies whenever currentui_root_path. The history shows which control stole focus and on which frame. (This is how the navbar-toggle focus-steal bug was found: the gate read true→false when a press landed focus on the navbar's full-rect Button.)

Streams — unified-tail.sh

scripts/unified-tail.sh                 # logs only (default)
scripts/unified-tail.sh log,network     # logs + HTTP
scripts/unified-tail.sh log,lifecycle   # logs + per-tick scene lifecycle

Content truncated.

When not to use it

  • When inspecting production builds, as `eval` is hard-disabled
  • When the client is not configured to dial out to a debug-hub
  • When only static code analysis is required without live client interaction

Limitations

  • The `eval` command for running GDScript is hard-disabled in production builds
  • GDScript `push_warning`/`push_error` are not captured, only Rust warn/error
  • Mobile's default log filter is `info`, requiring `--rust-log` for `debug!` lines

How it compares

This skill enables real-time inspection and GDScript execution on live mobile and desktop clients by having the client dial out to a hub, which differs from traditional debugging that requires dialing into a device.

Compared to similar skills

mobile-dev-debug-tool side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
mobile-dev-debug-tool (this skill)01moReviewAdvanced
react-native-best-practices52moReviewAdvanced
perf-optimizer51moReviewAdvanced
ios-debugger-agent25moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

react-native-best-practices

callstackincubator

Provides React Native performance optimization guidelines for FPS, TTI, bundle size, memory leaks, re-renders, and animations. Applies to tasks involving Hermes optimization, JS thread blocking, bridge overhead, FlashList, native modules, or debugging jank and frame drops.

530

perf-optimizer

OneKeyHQ

Systematic performance optimization and regression debugging for OneKey mobile app (iOS). Use when: (1) Fixing performance regressions - when metrics like tokensStartMs, tokensSpanMs, or functionCallCount have regressed and need to be brought back to normal levels, (2) Improving baseline performance - when there's a need to optimize cold start time or reduce function call overhead, (3) User requests performance optimization/improvement/debugging for the app's startup or home screen refresh flow.

513

ios-debugger-agent

Dimillian

Use XcodeBuildMCP to build, run, launch, and debug the current iOS project on a booted simulator. Trigger when asked to run an iOS app, interact with the simulator UI, inspect on-screen state, capture logs/console output, or diagnose runtime behavior using XcodeBuildMCP tools.

214

launch-on-device

zly2006

Build, install, and launch the Zhihu++ Android app on a connected device using ADB. Includes comprehensive troubleshooting for common issues like missing devices, installation failures, signature mismatches, and app crashes. Use when deploying debug builds to physical devices or emulators.

13

debug

woliveiras

>

00

metravel-android-developer

kelios

Implement and debug Android/native compatibility in the metravel Expo React Native app. Use when Codex needs an Android developer role for native crashes, platform-specific files, Expo native modules, Android navigation, push notifications, SecureStore, permissions, native map behavior, or web-first

00

Search skills

Search the agent skills registry