Skip to content

WSL→Windows COM: Access is denied / cannot regain port control after hung exec or hard-reset #3

Description

@bdbarnett

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

  1. 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
  2. REPL mirror also logs ClearCommError failed (PermissionError(13, 'Access is denied.', None, 5)) and sometimes GetOverlappedResult failed (...).
  3. 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).
  4. 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.
  5. 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)

  1. mpftp connect COM49 (WSL CLI → extension RPC).
  2. mpftp hard-reset then mpftp resume (board runs main.py with Wi‑Fi connect).
  3. 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).
  4. mpftp interrupt → often already Access is denied.
  5. mpftp soft-reset / put / execcould not take control… Access is denied.
  6. mpftp disconnect then mpftp connect COM49port 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.

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