VSCode nRF Connect extension doesn't find any devices if it was started without any usb devices present

We're trying out using devcontainers in WSL to develop Zephyr based software for our products. For flashing products from within the devcontainer we're connecting the corresponding usb devices (dev kits or J-Links) to wsl with usbip, like described on microsofts website. We're then binding the /dev/bus/usb directory to the container and running it in privileged mode to be able to access usb devices from within the container. This works quite well, but we have one small issue with it:

When WSL first starts and there aren't any usb devices connected to it yet, the /dev/bus/usb directory isn't created yet. But we would like to start the devcontainer already and then connect a usb device at a later point. This is why we're using a volume mount, because then docker will just create the directory. When we then connect a usb device to wsl it also shows up in the devcontainer and we can see it with "nrfutil device list" and flash it with "west flash".

However, the nrf connect extension doesn't recognize it. When clicking the refresh button in the device list, the following error in the extension host output gets printed:

2025-09-30 09:42:29.373 [error] Error: kill ESRCH
	at process.kill (node:internal/process/per_thread:235:13)
	at ol (/home/user/.vscode-server/extensions/nordic-semiconductor.nrf-connect-2025.9.798-linux-x64/dist/extension.js:298:4217)
	at kue.restartProcess (/home/user/.vscode-server/extensions/nordic-semiconductor.nrf-connect-2025.9.798-linux-x64/dist/extension.js:402:7782)
	at pce.refreshDevicesCommand (/home/user/.vscode-server/extensions/nordic-semiconductor.nrf-connect-2025.9.798-linux-x64/dist/extension.js:316:4845)
	at /home/user/.vscode-server/extensions/nordic-semiconductor.nrf-connect-2025.9.798-linux-x64/dist/extension.js:397:3433
	at n (/home/user/.vscode-server/extensions/nordic-semiconductor.nrf-connect-2025.9.798-linux-x64/dist/extension.js:290:10957)
	at Kb.h (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:112:41565)
	at Kb.$executeContributedCommand (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:112:42456)
	at j4.S (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:168242)
	at j4.Q (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:168022)
	at j4.M (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:167111)
	at j4.L (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:166349)
	at od.value (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:165013)
	at $.C (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:27:2373)
	at $.fire (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:27:2591)
	at vo.fire (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:9458)
	at od.value (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:391:8617)
	at $.C (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:27:2373)
	at $.fire (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:27:2591)
	at vo.fire (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:9458)
	at iv.A (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:12574)
	at od.value (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:10994)
	at $.C (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:27:2373)
	at $.fire (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:27:2591)
	at y5.acceptChunk (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:7941)
	at od.value (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:7227)
	at $.C (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:27:2373)
	at $.fire (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:27:2591)
	at pL.z (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:21744)
	at pL.acceptFrame (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:21550)
	at fL.n (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:20078)
	at file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:17310
	at Socket.t (file:///vscode/vscode-server/bin/linux-x64/e3a5acfb517a443235981655413d566533107e92/out/vs/workbench/api/node/extensionHostProcess.js:29:15251)
	at Socket.emit (node:events:518:28)
	at addChunk (node:internal/streams/readable:561:12)
	at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
	at Readable.push (node:internal/streams/readable:392:5)
	at TCP.onStreamRead (node:internal/stream_base_commons:189:23)

Steps to reproduce:

  • Start wsl and make sure no usb devices are attached to wsl
  • Create a new directory and copy the attached Dockerfile and .devcontainer.json into it
  • Open the directory with vscode and reopen it in the devcontainer when prompted
  • Once vscode finished initializing, attach a usb device for nrf connect to wsl (an nrf52840 dev kit in our case)
  • Observe that "nrfutil device list" finds the device:
    1050241217
    Product         J-Link
    Board version   PCA10056
    Traits          seggerUsb, usb, devkit, jlink
    
    Supported devices found: 1
    
  • Try to refresh the "Conntected Devices" in the nrf connect extension and observe the error message in the Output for the "Extension Host (Remote)"

Before attaching the usb device, "nrfutil device list" prints:

Error: Failed to enumerate devices

Caused by:
    /sys/bus/usb/devices/ not found (errno 2) (UsbLister)

It looks to me like refreshing the connected devices tries to kill the previous "nrfutil device list" process to restart it, which doesn't work because it doesn't exist anymore because it crashed the first time because /sys/bus/usb/devices/ didn't exist yet. So my naive solution would be to simply ignore any errors when trying to kill the old "nrfutil device list" process.

Since it doesn't let me attach the Dockerfile, here the contents of the Dockerfile:

# 0.27.5 is the last version that includes the Zephyr SDK 0.17.0, Zephyr SDK versions above that don't work with the nrf SDK 3.0.2
FROM zephyrprojectrtos/ci:v0.27.5

# nrfutil wants version 8.42
ARG JLINK_VERSION=8.42

# install packages
RUN <<EOT
    set -e
    apt-get update -y
    apt-get install -y --no-install-recommends \
        cppcheck \
        fish \
        less \
        udev \
        usbutils \
        stow
    apt-get autoremove -y
    apt-get clean -y
    rm -rf /var/lib/apt/lists/*
EOT

# install jlink
RUN <<EOT
    set -e
    apt-get update -y

    JLINK_DEB_FILE=JLink_Linux_V${JLINK_VERSION/./}_x86_64.deb

    wget --post-data="accept_license_agreement=accepted&non_emb_ctr=confirmed" https://www.segger.com/downloads/jlink/$JLINK_DEB_FILE
    # we can't install jlink directly, as it expects udevadm to run to reload udev rules after installing
    # so we install manually and remove the post-install script
    dpkg --unpack ./$JLINK_DEB_FILE
    rm -f /var/lib/dpkg/info/jlink.postinst
    apt-get install -f -y

    rm $JLINK_DEB_FILE

    apt-get clean -y
    rm -rf /var/lib/apt/lists/*
EOT

RUN pip3 install codechecker --no-cache-dir

# install nrf udev rules
RUN <<EOT
    set -e
    wget https://github.com/NordicSemiconductor/nrf-udev/releases/download/v1.0.1/nrf-udev_1.0.1-all.deb
    dpkg -i nrf-udev_1.0.1-all.deb
    rm nrf-udev_1.0.1-all.deb
EOT

# install nrfutil
RUN <<EOT
    set -e
    wget https://files.nordicsemi.com/artifactory/swtools/external/nrfutil/executables/x86_64-unknown-linux-gnu/nrfutil
    mv nrfutil /usr/bin/
    chmod +x /usr/bin/nrfutil
EOT

# install nrfutil device command as user
USER user
RUN <<EOT
    set -e
    nrfutil self-upgrade
    nrfutil install device
EOT
USER root

# persist command history across sessions
RUN mkdir /commandhistory && chown -R user:user /commandhistory
USER user
RUN <<EOT
    set -e
    # bash history
    touch /commandhistory/.bash_history
    ln -s /commandhistory/.bash_history /home/user/.bash_history

    # fish history
    mkdir -p /home/user/.local/share/fish
    touch /commandhistory/fish_history
    ln -s /commandhistory/fish_history /home/user/.local/share/fish/fish_history
EOT
USER root

And the .devcontainer.json file:

{
    "build": {
        "dockerfile": "Dockerfile"
    },
    "remoteUser": "user",
    "mounts": [
        // persist command history across sessions
        "source=commandhistory,target=/commandhistory,type=volume"
    ],
    "customizations": {
        "vscode": {
            "extensions": [
                "nordic-semiconductor.nrf-connect-extension-pack"
            ]
        }
    },
    "runArgs": [
        // Allow the container to use usb devices
        "--privileged",
        // This is a volume instead of a bind mount because the /dev/bus/usb directory doesn't
        // exist on the host if there's no usb device connected to the host and the bind mount
        // expects the directory to exist on the host, causing an error when trying to use the dev
        // container without any connected usb devices.
        // Binding the path as a volume fixes this, because the directory on the host will simply
        // be created if it doesn't exist.
        "--volume=/dev/bus/usb:/dev/bus/usb"
    ]
}

Related