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

  1. Install udev tooling in the image/host: Debian/Ubuntu `apt-get install -y systemd-udev`, Alpine `apk add eudev` (provides udevadm)
  2. Verify resolution inside the daemon's environment: `command -v udevadm` - if it lives in /sbin or /usr/sbin, extend the service PATH accordingly
  3. On platforms without udev (macOS, WSL1, minimal CI), disable the USB monitor or gate it behind a build tag / runtime capability check
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/04c50ea13562ae14. Report an issue: GitHub.