Summary
On WSL2 + Cursor, after a hung exec / run_script (or sometimes after hard_reset + resume), mpftp reports that it is still connected but cannot take control of the board. interrupt, soft_reset, put, and exec fail with Windows serial PermissionError(13, 'Access is denied.'). Subsequent connect then reports port is busy or locked even when no other app (Thonny, etc.) is using the port. Recovery often requires killing hung CLI processes, disconnect, and/or reloading the extension window — and sometimes still fails until the USB device is replugged.
Environment
- mpftp:
bdbarnett/mpftp @ 56178e7 (Cursor extension + CLI via TCP RPC 127.0.0.1:7429)
- Host: WSL2 (
Linux … microsoft-standard-WSL2) driving Windows COM via python.exe / mpremote
- Device: ESP32-S3 MicroPython on COM49 (WCH CH343,
USB VID:PID=1A86:55D3)
- Clients: Cursor agent CLI (
mpftp exec / run / interrupt / hard-reset / resume) sharing the extension session
Symptoms
- Session shows
connected: true for COM49, but control ops fail:
interrupt failed: WriteFile failed (PermissionError(13, 'Access is denied.', None, 5))
could not take control of the board (interrupt/raw REPL failed): WriteFile failed (PermissionError(13, 'Access is denied.', None, 5))
- Same for
soft_reset, fs_write / put, exec
- REPL mirror also logs
ClearCommError failed (PermissionError(13, 'Access is denied.', None, 5)) and sometimes GetOverlappedResult failed (...).
- After
disconnect, connect COM49 fails with:
failed to open COM49: port is busy or locked. Close Thonny, a serial monitor, another mpftp session…
even when nothing else holds the port (agent-only session).
- Related hang pattern:
run_script / long exec ends with timeout waiting for first EOF reception while the firmware is busy (e.g. Wi‑Fi main.py or a long import). After that timeout, the serial handle often enters the Access-denied state above.
- RPC can still answer
ping / agent_status while the COM handle is dead — UI/CLI think a session exists but cannot use the port.
Reproduction (typical)
mpftp connect COM49 (WSL CLI → extension RPC).
mpftp hard-reset then mpftp resume (board runs main.py with Wi‑Fi connect).
- Immediately
mpftp run / exec a script that imports heavy modules or blocks before printing (or wait until run_script hits timeout waiting for first EOF reception).
mpftp interrupt → often already Access is denied.
mpftp soft-reset / put / exec → could not take control… Access is denied.
mpftp disconnect then mpftp connect COM49 → port is busy or locked for an extended period.
Also seen after a stuck exec that never returns (CLI process hung >30–90s) while the extension still marks the session connected.
Evidence from ~/.mpftp/activity.log (excerpt)
2026-07-24T00:11:22Z run_script: timeout waiting for first EOF reception
2026-07-24T00:18:21Z exec: GetOverlappedResult failed (PermissionError(13, 'Access is denied.', None, 5))
2026-07-24T00:18:21Z interrupt: interrupt failed: WriteFile failed (PermissionError(13, 'Access is denied.', None, 5))
2026-07-24T00:18:22Z connect: failed to open COM49: port is busy or locked …
Earlier the same day (same machine/extension):
2026-07-24T00:01:52Z repl: ClearCommError failed (Access is denied)
2026-07-24T00:01:53Z fs_write / exec: WriteFile failed (Access is denied)
2026-07-24T00:02:00Z soft_reset: could not take control … Access is denied
2026-07-24T00:02:31Z resume: still connected but board not responding … Access is denied
Expected
- After
run_script/exec timeout or a hung board, interrupt should reliably reclaim the REPL (or hard-fail and release the Windows COM handle).
disconnect should always close/dispose the serial port so the next connect can open it.
- Session state should not remain
connected: true when WriteFile/ClearCommError are already Access denied.
- Prefer surfacing “stale handle / force reopen” recovery instead of a permanent busy lock until USB replug.
Notes / hypotheses for implementers
- WSL agent + Windows
python.exe serial path: overlapped I/O error may leave the handle open but unusable; disconnect path may not close it if interrupt/raw-REPL is wedged.
- Opening REPL on connect (recent behavior) plus concurrent RPC
exec/run_script may race on the same port.
hard_reset enumerates a new USB serial instance; resume reconnects, but a previous zombie handle can still own the old COM object briefly — recovery should force-close before reopen.
- EOF-wait timeout on
run_script may abandon the raw-REPL transaction without restoring friendly REPL / releasing exclusive access.
Happy to attach a longer activity.log slice if useful.
Summary
On WSL2 + Cursor, after a hung
exec/run_script(or sometimes afterhard_reset+resume), mpftp reports that it is still connected but cannot take control of the board.interrupt,soft_reset,put, andexecfail with Windows serialPermissionError(13, 'Access is denied.'). Subsequentconnectthen reports port is busy or locked even when no other app (Thonny, etc.) is using the port. Recovery often requires killing hung CLI processes,disconnect, and/or reloading the extension window — and sometimes still fails until the USB device is replugged.Environment
bdbarnett/mpftp@56178e7(Cursor extension + CLI via TCP RPC127.0.0.1:7429)Linux … microsoft-standard-WSL2) driving Windows COM viapython.exe/ mpremoteUSB VID:PID=1A86:55D3)mpftp exec/run/interrupt/hard-reset/resume) sharing the extension sessionSymptoms
connected: truefor COM49, but control ops fail:interrupt failed: WriteFile failed (PermissionError(13, 'Access is denied.', None, 5))could not take control of the board (interrupt/raw REPL failed): WriteFile failed (PermissionError(13, 'Access is denied.', None, 5))soft_reset,fs_write/put,execClearCommError failed (PermissionError(13, 'Access is denied.', None, 5))and sometimesGetOverlappedResult failed (...).disconnect,connect COM49fails with:failed to open COM49: port is busy or locked. Close Thonny, a serial monitor, another mpftp session…even when nothing else holds the port (agent-only session).
run_script/ longexecends withtimeout waiting for first EOF receptionwhile the firmware is busy (e.g. Wi‑Fimain.pyor a long import). After that timeout, the serial handle often enters the Access-denied state above.ping/agent_statuswhile the COM handle is dead — UI/CLI think a session exists but cannot use the port.Reproduction (typical)
mpftp connect COM49(WSL CLI → extension RPC).mpftp hard-resetthenmpftp resume(board runsmain.pywith Wi‑Fi connect).mpftp run/execa script that imports heavy modules or blocks before printing (or wait untilrun_scripthitstimeout waiting for first EOF reception).mpftp interrupt→ often alreadyAccess is denied.mpftp soft-reset/put/exec→could not take control… Access is denied.mpftp disconnectthenmpftp connect COM49→port is busy or lockedfor an extended period.Also seen after a stuck
execthat never returns (CLI process hung >30–90s) while the extension still marks the session connected.Evidence from
~/.mpftp/activity.log(excerpt)Earlier the same day (same machine/extension):
Expected
run_script/exectimeout or a hung board,interruptshould reliably reclaim the REPL (or hard-fail and release the Windows COM handle).disconnectshould always close/dispose the serial port so the nextconnectcan open it.connected: truewhen WriteFile/ClearCommError are already Access denied.Notes / hypotheses for implementers
python.exeserial path: overlapped I/O error may leave the handle open but unusable; disconnect path may not close it if interrupt/raw-REPL is wedged.exec/run_scriptmay race on the same port.hard_resetenumerates a new USB serial instance;resumereconnects, but a previous zombie handle can still own the old COM object briefly — recovery should force-close before reopen.run_scriptmay abandon the raw-REPL transaction without restoring friendly REPL / releasing exclusive access.Happy to attach a longer
activity.logslice if useful.