---
name: read-logs
description: Read the running Flutter app's latest run logs as context for a task — an auth flow, a crash, a race, "why did X happen at runtime". Use when the user invokes `/read-logs`, asks why the app behaved a certain way during a run, or wants the most recent run's logs as evidence. Reads only; never commits.
argument-hint: "[what to look for in the run logs]"
---

# Read run logs

> **⚠️ Reading a run sends its contents to the model — that *is* the leak, and it's how
> this skill works.** Run logs routinely contain auth/refresh/push tokens, emails, and
> customer data; the moment you read one, that data is sent to the model. Invoking
> `/read-logs` means **consciously accepting** that this captured run gets sent. Don't run
> it against production or real-customer data unless you've accepted that risk. (See the
> plugin README for redaction options and the full data-handling note.)

Load the running app's `flutter run` output as context for the task the user gave when
invoking this skill. Editor setup (making the app write the log file) is documented in this
plugin's `README.md` — that is canonical. The **First-run setup** section below intentionally
repeats those steps inline because this skill applies them at runtime, when the dev is in
their editor and not reading GitHub. Keep the two in sync.

Logs are captured per-project to `/tmp/flutter-<repo>.log` (`<repo>` derived from the shared
`.git` via `--git-common-dir`, so the main checkout and every git worktree resolve to the
*same* file), overwritten on every launch (always the latest run), living in `/tmp` —
outside the repo, nothing to gitignore. Two capture formats exist depending on editor; the
skill auto-detects which:
- **Zed / terminal run task:** raw `script` TTY transcript (plain text, ANSI codes).
- **VS Code / Cursor (`dart.dapLogFile`):** Debug Adapter Protocol log (JSON-framed; the
  app's output is inside `event:"output"` messages).

**This skill loads logs as CONTEXT — it is not a standalone analyzer.** Don't summarize the
run, surface all errors, or volunteer a diagnosis on your own. Read the logs so you have
them, then carry out the task you were invoked with (e.g. "why did login flash", "does auth
reach the API"). If no task was given, confirm the logs are loaded and ask what they want —
don't analyze unprompted.

---

## Step 1 — Resolve the path and check it exists (Bash)

```bash
# Worktree-stable name: derive from the shared .git so the main checkout and all its
# worktrees resolve to ONE file. (--git-common-dir is relative from the main repo and
# absolute from a worktree, so cd into it and resolve before taking the repo dir name.)
CDIR=$(git rev-parse --git-common-dir 2>/dev/null)
if [ -n "$CDIR" ]; then REPO=$(basename "$(dirname "$(cd "$CDIR" && pwd -P)")"); else REPO=$(basename "$(pwd)"); fi
L="/tmp/flutter-$REPO.log"
echo "$L"
if [ ! -f "$L" ]; then
  echo "MISSING"
else
  echo "exists — $(wc -l < "$L") lines"
  # Freshness: how old is the run, and did code change since?
  MT=$(stat -f %m "$L" 2>/dev/null || stat -c %Y "$L" 2>/dev/null)
  if [ -n "$MT" ]; then
    AGE_MIN=$(( ($(date +%s) - MT) / 60 ))
    echo "captured $(stat -f '%Sm' "$L" 2>/dev/null || stat -c '%y' "$L" 2>/dev/null) (${AGE_MIN} min ago)"
    [ "$AGE_MIN" -gt 15 ] && echo "⚠️ this run is ${AGE_MIN} min old"
  fi
  if [ -d lib ] && [ -n "$(find lib -name '*.dart' -newer "$L" 2>/dev/null | head -1)" ]; then
    echo "⚠️ Dart files in lib/ changed AFTER this run — the log predates your current code"
  fi
fi
```

- **Exists** → go to Step 2 (note any freshness ⚠️ for Step 3).
- **MISSING** → do NOT just report missing. Go to the **First-run setup** section.

## Step 2 — Read the run (format-aware, task-led)

The file holds **only the most recent run** (it's overwritten on every launch), so there
are no older runs to scroll past and no need for a flat tail. First normalize it to plain
text, then read it with the **task as your lens** — not a fixed filter.

**Normalize** (sniff the first lines for format) into a clean temp file you can page through:

```bash
CLEAN="${L%.log}.clean.log"
if head -50 "$L" | grep -qE '\[DAP\]|"event"[[:space:]]*:[[:space:]]*"output"'; then
  echo "[format: DAP — extracting app output]"
  # grep first: only the output-event lines reach the JSON parser, not the whole protocol —
  # so extraction cost tracks app output, not total DAP traffic. -E (not \|) for portable
  # alternation; [[:space:]]* tolerates non-compact JSON.
  grep -E '"event"[[:space:]]*:[[:space:]]*"output"' "$L" | python3 -c '
import json, sys
for line in sys.stdin:
    i = line.find("{")
    if i < 0: continue
    try: m = json.loads(line[i:])
    except Exception: continue
    if m.get("event") == "output":
        sys.stdout.write(m.get("body", {}).get("output", ""))
' > "$CLEAN" 2>/dev/null
  # jq fallback if python3 is unavailable:
  # grep -E '"event"[[:space:]]*:[[:space:]]*"output"' "$L" | sed -E 's/^[^{]*//' | jq -rj 'select(.event=="output") | .body.output' > "$CLEAN"
else
  echo "[format: plain transcript]"
  sed -E $'s/\x1b\\[[0-9;?]*[ -\\/]*[@-~]//g; s/\r$//' "$L" > "$CLEAN"
fi
wc -l "$CLEAN"
```

(If a DAP log can't be extracted because neither `python3` nor `jq` is present: `python3`
ships on most systems; otherwise install `jq` via the platform's package manager —
`brew install jq` on macOS, `apt install jq` / `dnf install jq` on Linux. Until then, fall
back to reading matching raw lines so the user isn't blocked.)

**Transparency:** before reading, surface one line to the dev — *"reading `<path>` — its
contents go to the model"* — so each read is a visible, conscious step (no blocking prompt).

**Then read `$CLEAN` with the task as the lens — go as deep as the task needs:**

- **Small run (roughly ≤ 600 lines):** read the **whole** file (`Read` it / `cat`). It's one
  run; the full thing is the cleanest context. No filtering.
- **Large run** (long session, many hot reloads): **don't dump it.** Investigate against the
  task — `grep` the cleaned text for what it's actually about, read those windows (`Read`
  with offset/limit, or `grep -B/-A`), and **iterate deeper** (widen context, follow the
  thread, read more) until you can answer. The lens comes from the task:
  - crash / "doesn't work" → error markers: `^E/flutter`, `[SEVERE]`, `Exception:`,
    `^#[0-9]+ ` (stack frames), `═══`, build failures (`FAILURE:`, `BUILD FAILED`,
    `Error (Xcode`, `lib/...: Error`).
  - race / ordering / timing → trace the relevant cubits/events/timestamps **in sequence**;
    error markers are irrelevant here.
  - a specific feature/screen → grep its names and read around them.

  Errors are just **one possible lens — never the only one.** Don't reduce every log read to
  error-hunting.

## Step 3 — Use as context

Carry out the task you were invoked with against the logs. No run summary or error report
unless the task asks for it.

If Step 1 flagged the run as stale — old, or (especially) Dart files changed after it was
captured — **and** the task is recency-sensitive ("does my fix work", "I just changed X"),
lead with that: tell the dev the log may not reflect their current code and suggest
re-running before you draw conclusions. It's a heads-up, not a refusal — if they clearly
want the existing run (e.g. investigating a past race), just proceed.

---

## First-run setup (log is MISSING)

Goal: leave the dev with their editor configured to capture logs, with no surprises. Never
guess silently and never commit anything. (The `README.md` is the canonical copy of these
steps; this inline version exists so setup works at runtime.)

**This is the conscious opt-in moment.** Before wiring anything up, make sure the dev
understands that enabling capture means Claude will read run logs into context — which
**sends them to the model** — and that runs can contain tokens and customer data. Get a
clear yes before applying the setup; if they prefer not to take that risk, don't wire it.

### 1. Determine which editor the dev runs the app in

Detection is a convenience to skip a question — never a hard dependency. In order:

```bash
echo "TERM_PROGRAM=$TERM_PROGRAM"   # vscode (also Cursor) | zed | iTerm.app | Apple_Terminal | tmux ...
test -d .zed && echo "has .zed"; test -d .vscode && echo "has .vscode"
```

- `$TERM_PROGRAM` clearly says **vscode** → VS Code/Cursor path. **zed** → Zed path.
- Inconclusive (plain terminal) → fall back to config dirs: only `.zed/` → Zed; only
  `.vscode/` → VS Code/Cursor.
- Still ambiguous (both dirs, or neither, and no host signal) → **ask** the dev which
  editor they launch the app in. Asking once here is fine — this is one-time setup.

Note: Cursor is a VS Code fork — it uses the same Dart-Code extension and reads
`.vscode/settings.json`, so it follows the VS Code path. A `.cursor/` dir is irrelevant to
logging.

### 2. Check whether logging is already wired (then decide)

The expected path is `L` from Step 1.

- **VS Code/Cursor** — Read `.vscode/settings.json`; logging is wired iff
  `"dart.dapLogFile"` equals `L`. (Legacy note: `dart.flutterRunLogFile` is **removed** in
  current Dart-Code — ignore it if present.)
- **Zed / terminal task** — Read `.zed/tasks.json` (or `.vscode/tasks.json` for a VS Code
  task runner); wired iff the `flutter run` tasks use `command: "script"` with `-q` `L`.

Then:
- **Already wired correctly** → setup is fine; the app just hasn't been launched since (or
  `/tmp` was cleared). Tell them to run the app, then re-run `/read-logs`.
- **Not wired** → explain what's missing and **offer to set it up** (next step). Don't edit
  until they agree.

### 3. Propose the fix (only after the dev agrees)

Pick by editor. For VS Code/Cursor, prefer `dapLogFile` (keeps the F5 / Run-and-Debug
workflow — it captures F5 **and** Ctrl+F5 debug sessions). Only use the `script` task
variant if they launch via a non-debug terminal task (then `dapLogFile` would stay empty).

**Before editing, check whether the target file is tracked — and warn if so:**

```bash
TARGET=.vscode/settings.json   # or .zed/tasks.json
git check-ignore -q "$TARGET" && echo "ignored (local-only — safe to edit)" || echo "TRACKED/committable — edit will show in git"
```

If TRACKED, tell the dev before applying: *"heads-up — `$TARGET` is tracked in this repo,
so this change will appear in `git status`. A `dapLogFile` line is a personal pref, and
wrapping committed run tasks in `script` changes the run command for the whole team —
commit it only if your team wants shared logging."* Then let them decide.

**VS Code / Cursor** — merge into `.vscode/settings.json` (never drop existing keys like
`dart.flutterSdkPath`); create the file with just these keys if absent:
```json
{
  "dart.dapLogFile": "<L>",
  "dart.maxLogLineLength": 1000000
}
```
(`maxLogLineLength` default is 2000 and truncates long lines — bump it so output/stack
traces aren't cut.) **Bake the resolved literal `<L>`** (the `--git-common-dir`-derived
path from Step 1) — do **not** use `dapLogFile`'s `${workspaceName}` variable: it expands to
the workspace folder, which differs per worktree and won't match the read derivation. Then:
reload the VS Code window, F5/Ctrl+F5 a run, re-run `/read-logs`.

  ⚠️ `dapLogFile` only captures **debug-adapter sessions** (F5 / Ctrl+F5), and app logs reach
  the editor only on **simulator / macOS / Android / the `chrome` device**. A `-d web-server`
  run sends app logs to the *browser's* console — the file will then hold build/launch
  output but no app logs. If a dev sees an empty/launch-only capture on web, that's why.

**Zed / terminal task** — wrap each `flutter run` task in `script`, **preserving its
existing flavor / target / dart-define args** (wrap what's there — don't invent args).
`script`'s syntax differs by OS, so detect it first: `uname -s`.
- **macOS (Darwin), BSD `script`:** set `command` to `"script"`, args become
  `["-q", "<L>", <existing command + its args>]` (logfile, then the command as trailing
  args). E.g. `["-q", "<L>", "flutter", "run", "-t", "lib/main.dart"]`.
- **Linux, GNU `script`:** the command goes through `-c` as a single string and the logfile
  is **last**: args become `["-q", "-c", "<existing command + args joined into one string>", "<L>"]`.
  E.g. `["-q", "-c", "flutter run -t lib/main.dart", "<L>"]`.
- **Windows:** there's no `script`; that dev should use the VS Code/Cursor `dapLogFile`
  path instead (it's cross-platform).

Then: launch the app once, re-run `/read-logs`.

If neither `.vscode/` nor `.zed/` exists and the dev's editor is unknown, ask which editor
they run the app in, then apply the matching setup above.
