Skip to content

Agent/CLI ergonomics: non-interrupting reads, a run→capture cycle, and Claude Code integration #6

Description

@bdbarnett

Context

I drove a Waveshare ESP32-P4 (720×720 DSI, C6 Wi-Fi) entirely through scripts/mpftp from a Claude Code session: detect → build firmware with user C modules → flash → provision Wi-Fi → mip install → run examples → instrument LVGL → diagnose two real bugs → rebuild → reflash → verify. It worked, and the firmware half in particular was excellent. Everything below is friction I hit doing that, ordered by how much time it cost.

1. A read that doesn't interrupt the running program (biggest one)

exec / get / put all enter the raw REPL, which Ctrl-Cs whatever is running and clears module state. Observing a running script is what kills it. Probe after probe appeared to "stall at stage 00" — every one of those stalls was my own get interrupting the script mid-import. I lost more time to this than to the actual bugs.

The workaround I converged on:

  1. script appends progress to /result.txt, one line per stage
  2. mpftp run -d COM4 script.py (--no-follow, returns at once)
  3. wait without touching the port
  4. get /result.txt

Requests:

  • mpftp watch-file /result.txt or a --no-interrupt read path that streams a board file without entering raw REPL.
  • Make the second port a first-class read channel. This board exposes role=repl (UART) + role=cdc_debug (native CDC); debug-tee is the right idea but see §5.
  • Failing both, document the write-to-file pattern in docs/agent-guide.md — it's the single most useful technique I found and I had to derive it.

2. One command for the run → wait → collect cycle

I typed this shape maybe thirty times:

mpftp hard-reset -d COM4; sleep 10
mpftp run -d COM4 probe.py; sleep 20
mpftp get -d COM4 /result.txt ./result.txt; cat ./result.txt

Proposal:

mpftp probe -d COM4 probe.py --reboot-first --capture /result.txt --wait 20

…emitting the captured file as JSON. This is the agent loop for anything that outlives a raw-REPL session.

3. --reboot-first, because stale state looks like breakage

Re-importing board_config re-initialises the display and leaves prior timers armed. Two or three iterations in, the board saturates and every symptom looks like a code bug. Brad had to tell me "you likely need to reboot between iterations" — after which a probe that had been failing passed immediately. A --reboot-first flag on run/exec (and a note in the guide) would save agents that whole detour.

Related: hard-reset runs main.py. When main.py is a previous app, every reset boots into it. A --skip-main / --safe-boot option, or a documented "rename main.py aside while iterating", would help.

4. WSLENV for micropython.exe (silent wrong-code bug)

Launching the Windows binary from WSL, it never receives MICROPYPATH unless WSLENV=MICROPYPATH/l is exported, and silently falls back to %USERPROFILE%\.micropython\lib. I spent a while convinced I'd broken appdev — the board was running a months-old installed copy. mpftp knows it's launching a Windows binary; it should set WSLENV itself, or warn when MICROPYPATH is set but unreachable by the target interpreter.

5. debug-tee didn't work from WSL

mpftp debug-tee COM42 --log-path /tmp/tee.log reported "log_path": "\\tmp\\tee.log" and no file appeared at /tmp/tee.log, /mnt/c/tmp/tee.log, or ~/.mpftp/. WSL path translation on the sidecar side. This is the feature that would have solved §1, so it's worth fixing first.

6. firmware build --clean doesn't clear a foreign CMake cache

The build dir held a cache pointing at /home/brad/gh/other/esp-idf (an SDK that had since moved). --clean ran idf.py fullclean, which refused ("doesn't seem to be a CMake build directory") and left the bootloader subproject cache intact; the next build failed on does not match the source ... used to generate cache. rm -rf build-<BOARD>-<VARIANT> fixed it. Suggest --clean fall back to removing the build dir when fullclean refuses, or a --distclean.

7. Machine-readable errors

Several failures came back as bare text where success is JSON — Expecting value: line 1 column 1 (char 0) from a wedged port, [Errno 2] /result.txt from a missing file. Agents parse JSON; a consistent {"ok": false, "error": ..., "hint": ...} envelope plus meaningful exit codes would let a tool retry intelligently instead of guessing.

Credit where due: "timeout waiting for first EOF reception" already does this well — it explains that the board is fine and busy, names --no-follow, and says the COM handle was released. More errors like that one.

8. Smaller things

  • put --verify returning a sha256 is genuinely reassuring — keep it, and consider --verify by default.
  • firmware detect reporting secure-boot / flash-encryption before an erase is exactly the right safety gate.
  • Board debris accumulates fast when probing (/probe_*.py, /result.txt). A mpftp clean --dry-run would be nice.
  • mip install defaulted to micropython.org/pi/v2 and reported Package not found for a package that exists on a different index; passing index= fixed it. Worth surfacing in the CLI as mpftp mip --index.

9. Claude Code integration

Brad raised this and I think it's right. The CLI is already agent-shaped — JSON out, one concern per subcommand, no interactive prompts. Two paths:

  • MCP server: expose ports / connect / run / probe / firmware * as tools. An agent gets typed schemas instead of parsing --help, and the non-interrupting read from §1 becomes a natural tool boundary.
  • Claude Code plugin: skills wrapping the workflows (flash this board, run this example, bisect this firmware) plus the agent guide as skill context. The write-to-file probe pattern is exactly the kind of hard-won technique a skill should encode so nobody re-derives it.

The docs are already unusually good for this — docs/agent-guide.md is written to agents, and the role=repl vs role=cdc_debug table answered my port question without asking. That foundation is why the session worked at all.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions