Summary
fbuild-daemon keeps an open fd on the serial port after a run finishes, so the next run fails with Could not open /dev/ttyACM0, the port is busy or doesn't exist. There is no supported way to clear it: fbuild daemon stop reports "daemon is not running" while the daemon is demonstrably alive and holding the fd.
On an unattended bench this reads as a wedged board. It is not — the device stays enumerated the whole time.
Evidence
After a failed autoresearch rp2350w --net-peer --ota run, the ESP32-C6 port would not open:
RuntimeError: attach failed: open_port(/dev/ttyACM0) exceeded 3s; serial driver may be wedged
The device was still enumerated (303a:1001), the node existed with correct ownership, and lsof reported nothing. Walking /proc/*/fd found the holder:
holder pid=414394 comm=fbuild-daemon
open() returned EBUSY while held. After killing the daemon it opened in 0.00 s (it had been taking 13.3 s while contended).
It reproduces. The next autoresearch invocation spawned a new daemon that did the same thing, breaking the run after it:
holder pid=434691 comm=fbuild-daemon age=179s
ERROR: A fatal error occurred: Could not open /dev/ttyACM0, the port is busy or doesn't exist.
Killing pid 434691 freed the port, and the following run deployed and passed RPC smoke normally.
The recovery path is missing
$ fbuild daemon stop
daemon is not running
$ ps -eo pid,etimes,comm | grep fbuild
434691 180 fbuild-daemon # holding /dev/ttyACM0
stop cannot see the process it needs to stop, so kill is the only way out. That is the same instance-visibility problem soldr has with a version-mismatched daemon, and it turns a transient leak into a hard stop for anyone who does not think to inspect /proc.
Impact
- Any run following a failed peer/OTA run fails on a port that is actually fine.
- The error message points at the serial driver and the device ("serial driver may be wedged"), not at the daemon holding the fd, so the natural next step is to power-cycle or reflash a healthy board.
- Unattended, it stops a bench until someone intervenes.
Suggested fixes
- Release the serial handle when the monitor/deploy session ends, including on the error paths — a failed OTA run is exactly when this leaked.
- Make
fbuild daemon stop find and stop a running daemon regardless of how it was started, or say which process it could not match instead of "not running".
- When an open fails with
EBUSY, name the holding pid rather than blaming the serial driver. The information is in /proc/*/fd and would have turned a long investigation into one line.
Environment
NixOS, Linux 6.18.48, x86_64, fbuild 2.5.23. ESP32-C6 (303A:1001) and RP2350W (2E8A:F00F) on the same host.
Related: #1424 (a healthy port reported unusable for a host-side reason — same family).
Generated with Claude Code
https://claude.ai/code/session_01KkufoNxfnNRU9psT3R9F51
Summary
fbuild-daemonkeeps an open fd on the serial port after a run finishes, so the next run fails withCould not open /dev/ttyACM0, the port is busy or doesn't exist. There is no supported way to clear it:fbuild daemon stopreports "daemon is not running" while the daemon is demonstrably alive and holding the fd.On an unattended bench this reads as a wedged board. It is not — the device stays enumerated the whole time.
Evidence
After a failed
autoresearch rp2350w --net-peer --otarun, the ESP32-C6 port would not open:The device was still enumerated (
303a:1001), the node existed with correct ownership, andlsofreported nothing. Walking/proc/*/fdfound the holder:open()returnedEBUSYwhile held. After killing the daemon it opened in 0.00 s (it had been taking 13.3 s while contended).It reproduces. The next
autoresearchinvocation spawned a new daemon that did the same thing, breaking the run after it:Killing pid 434691 freed the port, and the following run deployed and passed RPC smoke normally.
The recovery path is missing
stopcannot see the process it needs to stop, sokillis the only way out. That is the same instance-visibility problem soldr has with a version-mismatched daemon, and it turns a transient leak into a hard stop for anyone who does not think to inspect/proc.Impact
Suggested fixes
fbuild daemon stopfind and stop a running daemon regardless of how it was started, or say which process it could not match instead of "not running".EBUSY, name the holding pid rather than blaming the serial driver. The information is in/proc/*/fdand would have turned a long investigation into one line.Environment
NixOS, Linux 6.18.48, x86_64, fbuild 2.5.23. ESP32-C6 (
303A:1001) and RP2350W (2E8A:F00F) on the same host.Related: #1424 (a healthy port reported unusable for a host-side reason — same family).
Generated with Claude Code
https://claude.ai/code/session_01KkufoNxfnNRU9psT3R9F51