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
- Split the invocation: open local files and remote files in separate commands
- Remove the stray local path from the remote-targeted invocation
- Check each argument parses as the intended kind (ssh:// prefix or user@host:path for remote)
- 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
- Group arguments by kind before invoking the editor
- Check wrapper scripts that append local files unconditionally
- Verify each path parses as the intended local/remote kind
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
- Cannot open files from multiple remote hosts. First
- Too many '+ ' arguments (at most one is allowed)
- '+ ' requires a file argument (e.g. 'fresh + file.txt'…
- Cannot mix local and remote files. Use either local paths…
- --ssh-url expects an ssh:// URL, got
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)