sipeed/picoclaw · error
udevadm start: %w (is udevadm installed?)
Error message
udevadm start: %w (is udevadm installed?)
What it means
USBMonitor.Start runs `udevadm monitor --property --subsystem-match=usb` via exec.CommandContext and this error wraps cmd.Start() failing. The dominant cause is exec.LookPath failing to find udevadm in PATH (the message appends 'is udevadm installed?'). It can also fire when the passed ctx is already canceled before Start, or PATH resolution found a non-executable file.
Source
Thrown at pkg/devices/sources/usb_linux.go:64
func (m *USBMonitor) Kind() events.Kind {
return events.KindUSB
}
func (m *USBMonitor) Start(ctx context.Context) (<-chan *events.DeviceEvent, error) {
m.mu.Lock()
defer m.mu.Unlock()
// udevadm monitor outputs: UDEV/KERNEL [timestamp] action devpath (subsystem)
// Followed by KEY=value lines, empty line separates events
// Use -s/--subsystem-match (eudev) or --udev-subsystem-match (systemd udev)
cmd := exec.CommandContext(ctx, "udevadm", "monitor", "--property", "--subsystem-match=usb")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("udevadm stdout pipe: %w", err)
}
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("udevadm start: %w (is udevadm installed?)", err)
}
m.cmd = cmd
eventCh := make(chan *events.DeviceEvent, 16)
go func() {
defer close(eventCh)
scanner := bufio.NewScanner(stdout)
var props map[string]string
var action string
isUdev := false // Only UDEV events have complete info (ID_VENDOR, ID_MODEL); KERNEL events come first with less info
for scanner.Scan() {
line := scanner.Text()
if line == "" {
// End of event block - only process UDEV events (skip KERNEL to avoid duplicate/incomplete notifications)
if isUdev && props != nil && (action == "add" || action == "remove") {
if ev := parseUSBEvent(action, props); ev != nil {View on GitHub (pinned to 49183d7e8d)
Solutions
- Install udev tooling in the image/host: Debian/Ubuntu `apt-get install -y systemd-udev`, Alpine `apk add eudev` (provides udevadm)
- Verify resolution inside the daemon's environment: `command -v udevadm` - if it lives in /sbin or /usr/sbin, extend the service PATH accordingly
- On platforms without udev (macOS, WSL1, minimal CI), disable the USB monitor or gate it behind a build tag / runtime capability check
- Ensure the context passed to Start is not already canceled; check ctx.Err() before calling
Example fix
// before: monitor started unconditionally
ch, err := usbMonitor.Start(ctx)
if err != nil {
return err // "udevadm start: ... (is udevadm installed?)"
}
// after: probe capability first, degrade gracefully
if _, lookErr := exec.LookPath("udevadm"); lookErr != nil {
log.Printf("usb monitoring disabled: udevadm not found")
ch, err = nil, nil
} else {
ch, err = usbMonitor.Start(ctx)
} Defensive patterns
Strategy: validation
Validate before calling
func udevadmAvailable() bool {
_, err := exec.LookPath("udevadm")
return err == nil
}
// and before Start:
if ctx.Err() != nil {
return fmt.Errorf("usb monitor: context already done: %w", ctx.Err())
} Type guard
import "os/exec"
func isUdevadmMissing(err error) bool {
return errors.Is(err, exec.ErrNotFound) ||
(err != nil && strings.Contains(err.Error(), "is udevadm installed"))
} Try / catch
ch, err := monitor.Start(ctx)
if err != nil {
if isUdevadmMissing(err) {
// degrade: run without hotplug events rather than failing the whole app
log.Printf("usb hotplug disabled: %v", err)
ch = nil
} else {
return err
}
} Prevention
- Install systemd-udev (or eudev on Alpine) in minimal container images used on real hardware
- Gate the USB monitor behind an exec.LookPath check and a Linux build tag
- Keep the daemon PATH broad enough to include /usr/bin and /sbin where udevadm lives
- Never pass an already-canceled context to Start
When it happens
Trigger: Starting the USB device monitor in a minimal container without systemd/udev installed; on a non-systemd distro or Alpine without the eudev package; on macOS/WSL dev machines; in a daemon whose PATH does not include /usr/bin:/sbin; or calling Start with an already-canceled context.
Common situations: Docker images built FROM scratch/distroless/alpine missing systemd-udev; production code path tested first on a Mac; PATH overridden by a wrapper script to a minimal set; unit tests passing context.Background() canceled early.
Related errors
- udevadm stdout pipe: %w
- credential: cannot determine home directory: %w
- credential: enc:// passphrase required
- $EDITOR is not set
- failed to parse $EDITOR: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/04c50ea13562ae14.
Report an issue: GitHub.