zed-industries/zed · error

failed to upload file via STFP/SCP {} -> {}: {}

Error message

failed to upload file via STFP/SCP {} -> {}: {}

What it means

For single-file uploads over SSH, Zed tries SFTP and falls back to a plain scp command; when the scp fallback exits non-zero this error is raised with the CLI's stderr (the 'STFP' in the message is a source-level typo for SFTP). Neither SFTP nor SCP could transfer the file.

Source

Thrown at crates/remote/src/transport/ssh.rs:1290

            let stderr = String::from_utf8_lossy(&output.stderr);
            log::debug!(
                "failed to upload file via SFTP {src_path_display} -> {dest_path_str}: {stderr}"
            );
        }

        log::debug!("using SCP for file upload");
        let mut command = self.build_scp_command(src_path, &dest_path_str, None);
        let output = command.output().await?;

        if output.status.success() {
            return Ok(());
        }

        let stderr = String::from_utf8_lossy(&output.stderr);
        log::debug!(
            "failed to upload file via SCP {src_path_display} -> {dest_path_str}: {stderr}",
        );
        anyhow::bail!(
            "failed to upload file via STFP/SCP {} -> {}: {}",
            src_path_display,
            dest_path_str,
            stderr,
        );
    }

    async fn is_sftp_available() -> bool {
        which::which("sftp").is_ok()
    }
}

impl SshSocket {
    #[cfg(not(windows))]
    async fn new(options: SshConnectionOptions, socket_path: PathBuf) -> Result<Self> {
        Ok(Self {
            connection_options: options,
            envs: HashMap::default(),

View on GitHub (pinned to f4178619ac)

Solutions

  1. Run scp manually with the same src/dst shown in the message to reproduce and read the stderr
  2. Check remote writability and space: `df -h`, `touch <dest_dir>/probe`
  3. Guard remote shell rc files against non-interactive output — any stdout noise breaks SFTP/SCP
  4. Install openssh-client on the remote if scp/sftp binaries are missing

Example fix

# before: .bashrc prints a motd, sftp protocol desynchronizes
# ~/.bashrc (remote)
echo "welcome"   # remove/guard for non-interactive shells

# after
if [ -z "$PS1" ]; then return; fi
echo "welcome"
Defensive patterns

Strategy: retry

Validate before calling

async fn remote_accepts_uploads(socket: &SshSocket, shell: ShellKind, dest_dir: &str) -> bool {
    socket.run_command(shell, "which", &["scp"], true).await.is_ok()
        && socket
            .run_command(shell, "touch", &[&format!("{dest_dir}/probe")], true)
            .await
            .is_ok()
}

Try / catch

match upload_file(src, dest).await {
    Err(e) if e.to_string().contains("STFP/SCP") && attempts_left() => {
        // repair writability / shell noise, then retry the single-file upload once
        retry_after_repair().await
    }
    other => other,
}

Prevention

When it happens

Trigger: build_scp_command's scp invocation exits non-zero for the uploaded file: scp missing on either side, permission denied on the destination, disk full, or remote shell startup output corrupting the SFTP/SCP protocol channel.

Common situations: Remotes without openssh-clients; destination dirs writable-but-not-owned or on full filesystems; .bashrc/.profile echoing text that breaks sftp's strict protocol; filenames needing shell quoting.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/358c891c44633a33. Report an issue: GitHub.