# Debug App

> Use when the user has finished building a mobile app, started it with `npm run dev`, and wants the running app monitored for runtime errors AND silent failures (empty lists, blank screens, swallowed network errors) and fixed autonomously. Accepts a free-text symptom (e.g., `/debug-app "todos not appearing on home screen"`) to drive terminal-log diagnostics — injects temporary console.log statemen…

- **Type:** Skill
- **Install:** `agentstack add skill-leonardseo-power-platform-skills-codex-debug-app`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [LeonardSEO](https://agentstack.voostack.com/s/leonardseo)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [LeonardSEO](https://github.com/LeonardSEO)
- **Source:** https://github.com/LeonardSEO/power-platform-skills-codex/tree/main/plugins/mobile-app/skills/debug-app

## Install

```sh
agentstack add skill-leonardseo-power-platform-skills-codex-debug-app
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

**📋 Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** — read first.

# Debug App — Monitor & Fix

Monitor the running app by reading the Metro dev-server terminal output, detect runtime and bundle errors, and fix them autonomously by editing the affected files (or routing to the right skill when the fix belongs in a domain like Dataverse schema or auth registration). For silent failures, inject temporary `console.log` statements at data-path boundaries, read the Metro terminal for output, then clean them up after the root cause is fixed. Modeled on the upstream `app-debugger.agent.md` pattern — foreground loop, 5-second cadence, exit on 3 consecutive clean polls.

> **Dev-client limitation:** the standalone dev client outputs app/runtime logs, React errors, and Metro bundler output to the terminal running `npm run dev`. This includes host runtime diagnostics that use strings such as `[AuthProvider] MSAL init failed:`, `[bridge] fetch THREW for`, `[bridge] HTTP  for`, `[addAadAppToConnectionAcl] failed HTTP  for connection`, `[useConnectionRefs] could not verify connection ACLs; treating existing connections as setup-required`, and `[PAHost][ErrorBoundary] Unhandled JS error:`. There is no separate device log stream. All diagnosis happens by reading that terminal and, where needed, injecting strategic trace statements into source files.

## Subcommands (parsed from `$ARGUMENTS`)

| Form | Behavior |
|---|---|
| `/debug-app` (no args) | **Default — terminal log-driven mode.** Run Phase 0 (startup check), enter monitor loop. Log source is the Metro terminal (`BashOutput` on the `$METRO_TERMINAL_ID` recorded in `memory-bank.md` by `/create-mobile-app` Step 12). One read covers Metro bundler errors, app/runtime log lines (including host diagnostics), and red-box stack traces. If the terminal ID is not in `memory-bank.md`, ask the user which terminal is running `npm run dev` before starting. |
| `/debug-app ""` | **Symptom-driven mode** (recommended when there's a user-visible problem). Free-text symptom such as `"todos not appearing on home screen"`, `"login button does nothing"`, `"list empty after refresh"`. Run Phase 0 → Phase 0.5 (parse symptom → ask the user to reproduce/navigate → walk the likely data path from terminal traces) → enter monitor loop. Catches silent failures (empty lists, blank screens, swallowed errors) that pure log polling misses. |
| `/debug-app status` | Print current state (last poll, fixes applied this session, unresolved errors). Do NOT enter loop. |
| `/debug-app stop` | If a loop is in progress, the user can type "stop" or this command to exit. State files preserved at `.claude/debug-app/`. |

**Dispatch rule:** if `$ARGUMENTS` is non-empty and is not one of the reserved subcommand tokens (`status`, `stop`, `help`, `--help`, `-h`, `version`, `--version`), treat the entire string (everything after the command name; outer quotes optional) as the symptom and use symptom-driven mode. For `help` / `--help` / `-h`, print the subcommands table above and exit.

**Tip — "play around then debug":** in primary mode, `BashOutput($METRO_TERMINAL_ID)` returns Metro output accumulated since the last read. So if something weird just happened, keep using the app the way you would normally — then run `/debug-app` (or `/debug-app ""`) and the very first cycle will see the entire history of your session, not just what arrives after the skill starts. No need to reproduce the bug under the agent's eye.

## Core Principles

- **Foreground autonomous loop** — Once started, this skill owns the conversation until 3 consecutive clean polls confirm the app is healthy, the user types `stop`, or the escalation rule trips. **Do not run other skills concurrently** — they'll queue behind the loop.
- **Run AFTER the app is loaded** — `npm run dev` must be running and the simulator/device must have the app open. Phase 0 verifies this; the skill stops cleanly if no app is detected.
- **Native-only runtime target** — The app must be loaded in a native dev client on a device or simulator; Metro terminal output is the log source for that native session.
- **No web or direct Metro probes** — Do not use React Native Web, browser automation, `curl`, `fetch`, `WebFetch`, or any direct request to a Metro/localhost endpoint for runtime diagnosis. Read only the Metro terminal and source files.
- **No screen-by-screen verification** — Do not crawl routes or validate every screen. In symptom mode, focus only on the user-reported workflow and the terminal/source evidence needed to diagnose it.
- **One fix at a time** — Fully resolve one issue (context → fix → type-check → reload → re-poll) before starting the next. No batching.
- **Working-dir state** — All session state lives in `.claude/debug-app/` (gitignored): `fixes.md` for audit log, `unresolved.md` for escalations, `injected-logs.md` for tracking injected console.log statements. Survives across runs.
- **Reference resolution order** — For unfamiliar errors: in-repo references first ([skills/add-dataverse/references/dataverse-reference.md](${CLAUDE_SKILL_DIR}/../../skills/add-dataverse/references/dataverse-reference.md), etc.), then `mcp__microsoft-learn__microsoft_docs_search`, then general web search.

## Workflow — Task List First

Before entering the monitor loop, write a task list and keep it up to date:

```
- [ ] Verify dev server is running (BashOutput on Metro terminal — expect Metro banner)
- [ ] Capture baseline terminal state (read BashOutput, note most recent activity)
- [ ] (Symptom mode only) Phase 0.5: parse symptom → ask user to navigate → inject console.logs → read terminal → walk data path → clean up logs
- [ ] Monitoring cycle 1: collect → classify → fix if needed
- [ ] Monitoring cycle 2: collect → classify → fix if needed
- [ ] Monitoring cycle 3: collect → classify → fix if needed
      (add cycles as needed; stop after 3 consecutive clean cycles AND symptom resolved/flagged)
- [ ] Fix:  →   (one task per error found)
```

Mark each cycle complete (clean OR fixed) before starting the next.

---

## Phase 0 — Startup Check

Before entering the loop:

### 0.0 Resolve the Metro terminal

The Metro terminal is the **only** log source. The dev-player routes all JS output there.

1. Read `memory-bank.md` for the `Metro terminal id:` line (written by `/create-mobile-app` Step 12).
2. If found, call `BashOutput` against that id once. If it returns any Metro output (even just the banner), set `$METRO_TERMINAL_ID` and continue.
3. If `memory-bank.md` has no terminal id, or `BashOutput` returns "shell not found" / "no such background shell": ask the user:
   > "Which terminal is running `npm run dev`? I need its terminal ID to read Metro logs. If you started it in VS Code, look for the active terminal tab name."
   Wait for the user to provide the ID, then retry `BashOutput` against the provided id. Set `$METRO_TERMINAL_ID` and continue.

Record the resolved id in `fixes.md`:
```
[] Log source — Metro terminal $METRO_TERMINAL_ID
```

### 0.1 Ensure state directory

```bash
mkdir -p .claude/debug-app
touch .claude/debug-app/fixes.md
touch .claude/debug-app/unresolved.md
touch .claude/debug-app/injected-logs.md
rm -f .claude/debug-app/symptom-state    # per-session — Phase 0.5 rewrites it if symptom mode is active
```

If `fixes.md` is empty, write a session header:
```
# Debug session — 

```

### 0.2 Verify Metro bundled and the app is running

Branch on the source resolved in 0.0.

**If `$METRO_TERMINAL_ID` is set (primary path):**

Call `BashOutput` on it once and scan the captured Metro output:

- Most recent error-class line is `SyntaxError`, `Unable to resolve module`, `transform failed`, or `error: Bundling failed` → bundle is broken. Treat as a Step B "Import / Bundle" critical error and route through Step D immediately. Do NOT enter the steady-state loop until the bundle is healthy.
- Output contains `Bundling complete` / `iOS Bundled` / `Android Bundled` with no later error-class line → Metro is healthy. Proceed.
- Output contains a Metro banner (`Metro waiting on`, `Logs for your project`) but no native `Bundled` / `bundling` lines yet → Metro is up but no native client has connected. Tell the user:
  > **Metro is running but no app is connected yet.** Open the app on a device or simulator, then re-run `/debug-app`.
  Stop here.
- Output is empty, OR contains no Metro banner at all → the recorded shell is alive but Metro isn't running in it (the user repurposed the terminal). Tell the user:
  > **Metro not detected in the recorded terminal.** Restart with `npm run dev` and re-run `/debug-app` — the new terminal id will be picked up from `memory-bank.md`.
  Stop here.

**If `$METRO_TERMINAL_ID` is NOT set:**

Ask the user:
> "Which terminal is running `npm run dev`? Please provide the terminal ID so I can read Metro output."

Wait for the user to reply. Set `$METRO_TERMINAL_ID` to the provided ID, call `BashOutput($METRO_TERMINAL_ID)` once, and continue with the checks above.

### 0.3 Capture baseline

Read the latest output from `BashOutput($METRO_TERMINAL_ID)`. Note the most recently bundled native platform (iOS / Android) and any recent runtime log lines. Append to `fixes.md`:
```
[] Baseline — last Metro activity: 
```

### 0.4 Initialize cursor

`BashOutput` maintains an internal stream cursor against `$METRO_TERMINAL_ID` — each call returns only output produced since the previous call. No separate cursor file is needed. The `.claude/debug-app/cursor` file is no longer used and can be ignored if present from a previous session.

---

## Phase 0.5 — Symptom-driven setup (only when `$ARGUMENTS` is a symptom string)

Skip this entire phase if no symptom was provided. The standard log-polling loop alone is good at *visible* errors but blind to *silent* ones: an empty list because the connector wasn't added, a blank screen because `useFocusEffect` wasn't wired, blank rows because column names don't match the model. Phase 0.5 closes that gap.

### 0.5.1 Parse the symptom

Extract three signals from the user's text:

| Signal | How to derive |
|---|---|
| **Affected screen** | Match keywords against route filenames in `app/` (e.g., `"todos"` → `app/(tabs)/todos.tsx`, `app/todos/index.tsx`, `app/(tabs)/index.tsx`). Use `Glob` to enumerate `app/**/*.tsx`; pick the closest substring match. If multiple, ask once. |
| **Affected entity / service** | Same keyword against `src/generated/services/*Service.ts` and `src/generated/models/*Model.ts` (e.g., `"todos"` → `TodosService`, `Todo` model). Use `Glob`. |
| **Symptom class** | Map the text to one of: `empty-list`, `blank-screen`, `wrong-data`, `unresponsive-control`, `stale-data`, `wrong-navigation`, `crash`, `pdf-viewer`, `pdf-report`, `pen-input`, `geolocation`, `dataverse-upload`. Default for "PDF won't open / preview PDF fails": `pdf-viewer`. Default for "report PDF not generated / print report fails": `pdf-report`. Default for "signature / pen / ink fails": `pen-input`. Default for "location not tracking / GPS not updating / background location stopped / breadcrumb gaps / route not consistent": `geolocation`. Default for "signature/report saved but missing", or "location rows not reaching Dataverse": `dataverse-upload`. Default for "not appearing / not showing / nothing here / missing": `empty-list`. Default for "doesn't load / freezes / spinner forever": `blank-screen`. |

Append to `fixes.md`:
```
[] Symptom — class= screen= entity=
```

If no screen/entity match: keep `screen=unknown` / `entity=unknown` and proceed — Phase 0.5 still injects diagnostic logs and reads the terminal from whatever data path is most likely based on the symptom text.

### 0.5.2 Ask the user to navigate to the affected screen

The dev-player has no automation API for navigation. Ask the user:
> "Please open the `` screen on the device/simulator, then reply `ready`."

Wait for the user to confirm before proceeding.

### 0.5.3 Inject diagnostic console.log statements and read terminal

Inject targeted `console.log` statements at the boundaries of the suspected data path so the Metro terminal reveals what's happening.

**Injection sites — choose the minimum set that covers the symptom class:**

| Symptom class | Inject at |
|---|---|
| `empty-list` | (a) entry point of the data-fetching hook, logging `[TRACE items]` the raw response length; (b) the screen component, logging `[TRACE render]` the `items` array length before the list renders |
| `blank-screen` | Entry point of the screen component, logging `[TRACE mount]` a timestamp and any auth/data props passed in |
| `wrong-data` / `stale-data` | The hook that calls the generated service (NOT inside `src/generated/`), logging `[TRACE service-response]` the raw return value |
| `unresponsive-control` | The event handler (`onPress`, `onSubmit`, etc.) logging `[TRACE handler-called]` before any async work |
| `crash` | Skip injection — jump to the monitor loop (Step A), crash stacks appear in the terminal |

**Console.log injection pattern — all injected lines MUST use this exact format:**

```ts
console.log('[TRACE ]', ); // [INJECTED-TRACE]
```

- `` — short unique label for this site (e.g., `items`, `render`, `service-response`)
- `// [INJECTED-TRACE]` trailing comment on the SAME LINE — this is the cleanup grep key
- Log the smallest useful value; use `JSON.stringify(value)` for objects
- **Never inject inside `src/generated/`** — inject in the hook/screen that calls into it

Record every injection in `.claude/debug-app/injected-logs.md`:
```
[] Injected [INJECTED-TRACE] at : — tag=
```

Then tell the user:
> "I've added diagnostic console.log statements. Please reload the app (press `r` in the Metro terminal), navigate to ``, and trigger the symptom (e.g., scroll the list, tap the button). Reply `done` when finished."

Wait for the user to reply, then call `BashOutput($METRO_TERMINAL_ID)` and filter for `[TRACE` lines.

### 0.5.4 Walk the data path from terminal output

Use the `[TRACE` lines to walk the chain:

1. **Screen TSX** (`app/.tsx`)
   - Find the `useListData(...)` / `use*Data(...)` call.
   - Check service-call options — a stray `top: 0`, an over-strict `filter`, a `search: query` bound to a never-cleared input, or `orderBy` on a missing column can each silently return zero rows.
   - Check any client-side `.filter(...)` after the data lands.

2. **Data hook** (`src/hooks/useListData.ts` or sibling)
   - **Critical:** the template hook has TWO mock-fallback paths:
     - **Error path**: service returns `{ error }` → hook substitutes mock AND may call `setError`. **Silent** if the screen ignores `error`.
     - **Empty-result path**: service returns `{ data: [] }` (no error) → hook silently substitutes mock. Always invisible without a `[TRACE]` log.
   - Detect: `Grep` for `MOCK_` imports in the screen file. If present, mock data is wired in.
   - Confirm `useFocusEffect` is used (not `useEffect`) — `useEffect` won't re-run on back-navigate.

3. **Generated service** (`src/generated/services/Service.ts`)
   - If a TODO stub or file missing → route to `/add-connector` or `/add-dataverse`. Do NOT edit `src/generated/`.
   - If it exists and the `[TRACE service-response]` log shows an error field → read that error; 401/403 = auth issue; 404 = wrong resource name.

4. **Generated model** (`src/generated/models/Model.ts`)
   - Confirm field names match what the screen references. `item.title` vs `cr3e9_title` produces blank rows.

5. **`power.config.json`**
   - Confirm the `datasources` array contains the suspected entity / connector. If absent, `npx power-apps add-data-source` was never run for it.

6. **Auth state** (`src/playerConfig.ts`, `app.config.js`, `auth.config.json`, `useAuth()` hook)
   - 401 from the service wrapped as `{ error }` — the `[TRACE service-response]` log surfaces the error string.
   - **OAuth deeplink handoff**: verify `app.config.js` → `expo.scheme` matches `src/playerConf

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [LeonardSEO](https://github.com/LeonardSEO)
- **Source:** [LeonardSEO/power-platform-skills-codex](https://github.com/LeonardSEO/power-platform-skills-codex)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-leonardseo-power-platform-skills-codex-debug-app
- Seller: https://agentstack.voostack.com/s/leonardseo
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
