sinelaw/fresh · error

Cannot mix local and remote files. Use either local paths…

Error message

Cannot mix local and remote files. Use either local paths or remote paths (ssh:// or user@host:path).

What it means

An invocation may contain either local file paths or remote paths (ssh:// or user@host:path), not both. After parsing, if any location is Local while others are remote, the parser bails. This prevents ambiguous session semantics (local buffers vs remote SSH sessions in one window).

Solutions

  1. Split the invocation: open local files and remote files in separate commands
  2. Remove the stray local path from the remote-targeted invocation
  3. Check each argument parses as the intended kind (ssh:// prefix or user@host:path for remote)
  4. Update wrapper scripts that always append a local file

Example fix

// before
fresh notes.txt deploy@server:/var/log/app.log
// after
fresh notes.txt
fresh deploy@server:/var/log/app.log
Defensive patterns

Strategy: validation

Validate before calling

let args: Vec<&str> = std::env::args().skip(1).collect();
let has_remote = args.iter().any(|a| a.starts_with("ssh://") || a.contains('@'));
let has_local = args.iter().any(|a| !a.starts_with("ssh://") && !a.contains('@') && std::path::Path::new(a).exists());
if has_remote && has_local { eprintln!("do not mix local and remote paths"); }

Type guard

fn is_remote_spec(s: &str) -> bool { s.starts_with("ssh://") || (s.contains('@') && s.contains(':')) }

Try / catch

match editor::launch(args) {
    Err(e) if e.to_string().contains("mix local and remote") => eprintln!("split into separate invocations"),
    Err(e) => eprintln!("{e}"),
    Ok(()) => {},
}

Prevention

When it happens

Trigger: Invoking e.g. `fresh notes.txt alice@host:/tmp/x.txt` — parsed locations contain both ParsedLocation::Local and ParsedLocation::Remote variants.

Common situations: Shell completion or scripts appending a local file to a remote-targeted command; habitually adding `.` or a local scratch file alongside ssh targets; mis-remembered syntax where a path looks remote but parses as local (or vice versa).

Related errors


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/6bc515de4826541a. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-editor/src/main.rs:1440

    }

    let first = remotes[0];
    for r in &remotes[1..] {
        if r.user != first.user || r.host != first.host || r.port != first.port {
            anyhow::bail!(
                "Cannot open files from multiple remote hosts. First: {}@{}, found: {}@{}",
                first.user,
                first.host,
                r.user,
                r.host
            );
        }
    }
    if parsed
        .iter()
        .any(|loc| matches!(loc, ParsedLocation::Local(_)))
    {
        anyhow::bail!(
            "Cannot mix local and remote files. Use either local paths or remote paths (ssh:// or user@host:path)."
        );
    }

    Ok(Some(remote_location_to_ssh_url(first)))
}

/// Parse a standalone `ssh://…` URL passed via the internal
/// `--ssh-url` flag.  Accepts only the URL form (not scp-style) and
/// the URL must carry a path; anything else is a hard error because
/// this input comes from our own `spawn_server_detached` and a
/// malformed URL there means we corrupted it on the way over.
fn parse_ssh_url_arg(url: &str) -> AnyhowResult<RemoteLocation> {
    let rest = url
        .strip_prefix("ssh://")
        .ok_or_else(|| anyhow::anyhow!("--ssh-url expects an ssh:// URL, got {:?}", url))?;
    parse_ssh_url_rest(rest, default_ssh_user().as_deref()).map_err(|reason| {
        anyhow::anyhow!(

View on GitHub (pinned to 67894ca546)