{"record":{"id":"04c50ea13562ae14","repo":"sipeed/picoclaw","slug":"udevadm-start-w-is-udevadm-installed","errorCode":null,"errorMessage":"udevadm start: %w (is udevadm installed?)","messagePattern":"udevadm start: %w \\(is udevadm installed\\?\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/devices/sources/usb_linux.go","lineNumber":64,"sourceCode":"func (m *USBMonitor) Kind() events.Kind {\n\treturn events.KindUSB\n}\n\nfunc (m *USBMonitor) Start(ctx context.Context) (<-chan *events.DeviceEvent, error) {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\t// udevadm monitor outputs: UDEV/KERNEL [timestamp] action devpath (subsystem)\n\t// Followed by KEY=value lines, empty line separates events\n\t// Use -s/--subsystem-match (eudev) or --udev-subsystem-match (systemd udev)\n\tcmd := exec.CommandContext(ctx, \"udevadm\", \"monitor\", \"--property\", \"--subsystem-match=usb\")\n\tstdout, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"udevadm stdout pipe: %w\", err)\n\t}\n\n\tif err := cmd.Start(); err != nil {\n\t\treturn nil, fmt.Errorf(\"udevadm start: %w (is udevadm installed?)\", err)\n\t}\n\n\tm.cmd = cmd\n\teventCh := make(chan *events.DeviceEvent, 16)\n\n\tgo func() {\n\t\tdefer close(eventCh)\n\t\tscanner := bufio.NewScanner(stdout)\n\t\tvar props map[string]string\n\t\tvar action string\n\t\tisUdev := false // Only UDEV events have complete info (ID_VENDOR, ID_MODEL); KERNEL events come first with less info\n\n\t\tfor scanner.Scan() {\n\t\t\tline := scanner.Text()\n\t\t\tif line == \"\" {\n\t\t\t\t// End of event block - only process UDEV events (skip KERNEL to avoid duplicate/incomplete notifications)\n\t\t\t\tif isUdev && props != nil && (action == \"add\" || action == \"remove\") {\n\t\t\t\t\tif ev := parseUSBEvent(action, props); ev != nil {","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/devices/sources/usb_linux.go#L46-L82","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: monitor started unconditionally\nch, err := usbMonitor.Start(ctx)\nif err != nil {\n    return err // \"udevadm start: ... (is udevadm installed?)\"\n}\n\n// after: probe capability first, degrade gracefully\nif _, lookErr := exec.LookPath(\"udevadm\"); lookErr != nil {\n    log.Printf(\"usb monitoring disabled: udevadm not found\")\n    ch, err = nil, nil\n} else {\n    ch, err = usbMonitor.Start(ctx)\n}","handlingStrategy":"validation","validationCode":"func udevadmAvailable() bool {\n    _, err := exec.LookPath(\"udevadm\")\n    return err == nil\n}\n\n// and before Start:\nif ctx.Err() != nil {\n    return fmt.Errorf(\"usb monitor: context already done: %w\", ctx.Err())\n}","typeGuard":"import \"os/exec\"\n\nfunc isUdevadmMissing(err error) bool {\n    return errors.Is(err, exec.ErrNotFound) ||\n        (err != nil && strings.Contains(err.Error(), \"is udevadm installed\"))\n}","tryCatchPattern":"ch, err := monitor.Start(ctx)\nif err != nil {\n    if isUdevadmMissing(err) {\n        // degrade: run without hotplug events rather than failing the whole app\n        log.Printf(\"usb hotplug disabled: %v\", err)\n        ch = nil\n    } else {\n        return err\n    }\n}","preventionTips":["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"],"tags":["linux","udev","devices","environment","containers","usb"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}