zeroclaw-labs/zeroclaw · error

Upload failed: {} Ensure the board is connected and the por

Error message

Upload failed:
{}

Ensure the board is connected and the port is correct (e.g. /dev/cu.usbmodem* on macOS).

What it means

flash_arduino_firmware uploads with `arduino-cli upload -p <port> --fqbn arduino:avr:uno`. This bail fires when the upload exits non-zero; arduino-cli/avrdude stderr is embedded plus a hint about board connection and port naming. The port comes from your argument or from resolve_port() (the path configured on the arduino-uno serial board).

Source

Thrown at crates/zeroclaw-hardware/src/peripherals/arduino_flash.rs:132

    if !compile.status.success() {
        let stderr = String::from_utf8_lossy(&compile.stderr);
        let _ = std::fs::remove_dir_all(&temp_dir);
        anyhow::bail!("Compile failed:\n{}", stderr);
    }

    // Upload
    println!("Uploading to {}...", port);
    let upload = Command::new("arduino-cli")
        .args(["upload", "-p", port, "--fqbn", FQBN, &*sketch_path])
        .output()
        .context("arduino-cli upload failed")?;

    let _ = std::fs::remove_dir_all(&temp_dir);

    if !upload.status.success() {
        let stderr = String::from_utf8_lossy(&upload.stderr);
        anyhow::bail!(
            "Upload failed:\n{}\n\nEnsure the board is connected and the port is correct (e.g. /dev/cu.usbmodem* on macOS).",
            stderr
        );
    }

    println!("ZeroClaw firmware flashed successfully.");
    println!("The Arduino now supports: capabilities, gpio_read, gpio_write.");
    Ok(())
}

/// Resolve port from config or path. Returns the path to use for flashing.
pub fn resolve_port(
    config: &zeroclaw_config::schema::Config,
    path_override: Option<&str>,
) -> Option<String> {
    if let Some(p) = path_override {
        return Some(p.to_string());
    }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Run `arduino-cli board list` (replug the board if needed) and pass the port it reports
  2. Close every program holding the port (Arduino IDE serial monitor, screen, the running zeroclaw runtime) and retry
  3. On Linux, fix permissions: `sudo usermod -aG dialout $USER` then log out and back in
  4. Read the embedded stderr: 'device not responding' means wrong port/board, 'permission denied' means OS access
  5. Use OS-correct naming: /dev/cu.usbmodem* on macOS, /dev/ttyACM* on Linux, COM* on Windows

Example fix

# before
[[peripherals.boards]]
board = "arduino-uno"
transport = "serial"
path = "/dev/ttyUSB0"

# after (port reported by `arduino-cli board list`)
[[peripherals.boards]]
board = "arduino-uno"
transport = "serial"
path = "/dev/ttyACM0"
Defensive patterns

Strategy: validation

Validate before calling

// port must exist and be a char device (unix) before uploading
let meta = std::fs::metadata(port)
    .map_err(|_| anyhow::anyhow!("port {port} does not exist; check `arduino-cli board list`"))?;
#[cfg(unix)]
assert!(use std::os::unix::fs::FileTypeExt::is_char_device(&meta.file_type()));
flash_arduino_firmware(port)?;

Try / catch

match flash_arduino_firmware(port) {
    Err(e) if format!("{e}").contains("Upload failed") => {
        // embedded stderr + hint: wrong port, busy port, or permissions;
        // re-run `arduino-cli board list`, close port holders, check dialout group
    }
    rest => rest,
}

Prevention

When it happens

Trigger: Uploading to a port that does not exist or is not the board (/dev/ttyUSB1 vs actual /dev/ttyACM0, wrong COM number), the board being unplugged, the port held open by another program (Arduino IDE serial monitor, screen, an already-running zeroclaw SerialPeripheral), or the OS user lacking permission on the device node.

Common situations: Device name changed after replug; Linux user not in the dialout group (avrdude: permission denied); flashing while the runtime already holds the port open; using /dev/tty.* naming on Linux or vice versa.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/d908ee7fb09c564b. Report an issue: GitHub.