agentleFS
parawandererparawanderer/OpenTagViewer/.claude/skills/watch-gradle-tests/SKILL.md

watch-gradle-tests

Skill455 starsChanged 33 days agoMITView on GitHub

Watch a Gradle instrumented-test run to a verdict without hand-writing greps. Use whenever running :app:testEmulatorDebugAndroidTest or :app:connectedDebugAndroidTest in the background.

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 3 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    A watcher that can exit silently is not a watcher: If yours can end without printing, make the last thing it does print where the run got …

    line 64
  2. Distinctive:

    All three exited 0. Silence from a monitor is indistinguishable from silence from a healthy run, so a red suite was reported as green until …

    line 78
  3. Distinctive:

    A stall is a result: No output for eight minutes gets reported, with a diagnosis, rather than looking like a slow test for another twenty.

    line 93

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Watching a test run to a verdict

The emulator suite takes upwards of ten minutes, so it gets backgrounded, so something has to report on it. Do not write that something fresh each time. Use the script.

# 1. Start the run, redirected to a file.
./gradlew :app:testEmulatorDebugAndroidTest --console=plain > tmp/run.log 2>&1

# 2. Prove the watcher sees the log before trusting it. One command, always.
python .claude/skills/watch-gradle-tests/watch_tests.py tmp/run.log --once

# 3. Arm a Monitor on the same script with no --once.
python .claude/skills/watch-gradle-tests/watch_tests.py tmp/run.log

Each line it prints is one event: FAILED <Class>.<method> as each failure appears, STALLED … if the log stops growing, and FINISHED + VERDICT at the end.

Give every run its own log file

Reusing one name races the watcher against the run that is starting. Arm a Monitor while the previous run's log is still on disk and it reads that — matches its BUILD line, prints its verdict, and reports a finished run that has not started. The XML age in VERDICT is the only hint, and "2 min old" looks perfectly current.

That cost a wrong conclusion here: a stale verdict was read as the new run's, its crash trace pointed at a line number the fix had already moved, and the obvious inference — "the APK did not rebuild" — was wrong twice over.

log=tmp/run-$(date +%H%M%S).log
./gradlew :app:testEmulatorDebugAndroidTest --console=plain > "$log" 2>&1 &
python .claude/skills/watch-gradle-tests/watch_tests.py "$log"

Never wrap --once in your own sleep loop

That is the shape step 3 exists to replace, and it looks close enough to right to pass review:

# WRONG - and this exact loop cost 1h22m
for i in $(seq 1 110); do
  grep -qE "BUILD SUCCESSFUL|BUILD FAILED" tmp/run.log && { ...--once; break; }
  sleep 20
done

It waits for a terminal line and nothing else, so it is blind to the run stopping without one — which is the failure worth catching. A suite hung 25 minutes on a single test produced no new output and no verdict, so the loop sat silent, then hit its own limit and exited 0 with no output at all: indistinguishable from success. Meanwhile watch mode would have said STALLED no output for 8 min, at 424/687 seventeen minutes earlier.

Two rules follow, and they are the same rule twice:

  • Watch progress, not just completion. "Still running" and "wedged" look identical unless something is measuring the gap between outputs.
  • A watcher that can exit silently is not a watcher. If yours can end without printing, make the last thing it does print where the run got to.

Why this exists

Three hand-written monitors in one afternoon each matched nothing, and each looked like a green run:

What was writtenWhy it matched nothing
grep "(0 skipped)"the run had 5 skipped
grep "\S+ \[testEmulator\]"there is no space before [testEmulator]
... | head -25truncated before the verdict, and head's exit code hid it

All three exited 0. Silence from a monitor is indistinguishable from silence from a healthy run, so a red suite was reported as green until the log was read by hand. That is the whole argument for a script: the patterns get fixed once, and --once proves they still match before anything depends on them.

What it knows that a grep does not

  • The XML has the last word. The console can truncate, interleave, or count a retry (605/600 is a real line from this repo). verdict_from_xml reads app/build/outputs/androidTest-results/, and scopes to the newest run's own directory — AGP keeps connected/ and managedDevice/ side by side and clears neither, so summing everything reported a 22-test run as 74. It always prints the results' age, because a verdict that is quietly ten minutes old is the same bug wearing a coat.
  • No results at all is not zero failures. A run that dies before any test reports writes no XML, and reading that as success is exactly how a red build gets called green.
  • A stall is a result. No output for eight minutes gets reported, with a diagnosis, rather than looking like a slow test for another twenty.

The two silent hangs, which need opposite fixes

The script separates them by whether any test has reported. Do not guess between them — the distinction is in the output.

SymptomCauseFix
No test ever reports, no managed AVD in adb devicesleaked managed-device slots — MDLockCount above zero with nothing running./gradlew --stop && rm ~/.android/avd/gradle-managed/active_gradle_devices
Tests ran, then stopped mid-suitethe device died under themadb logcat -b crash — the emulator's Bluetooth stack aborting has done this here
connectedDebugAndroidTest fails to install or hangs at oncestale ADB bridge inside the Gradle daemonrestart the emulator and ./gradlew --stop — either alone leaves it broken

Killing a run is what leaks a slot, and every Ctrl-C adds one. Prefer letting a run finish; if you must kill one, clear the count in the same breath rather than meeting it next time.

Related

  • AGENTS.md, "Building and testing" — the managed device, and why it beats a hand-started emulator.
  • .claude/skills/watch-pr/ — the same discipline for CI: prove the poll body emits before arming a Monitor on it.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.