sinelaw/fresh · error
--ssh-url is not a valid ssh:// URL
Error message
--ssh-url is not a valid ssh:// URL ({}): {:?} What it means
After the ssh:// prefix check passes, parse_ssh_url_arg delegates to parse_ssh_url_rest to split user@host:port/path into a RemoteLocation. If that parse fails (no host, bad port, empty path where a path is required), the reason is wrapped into this error. The doc notes a missing path is fatal because the URL comes from spawn_server_detached and malformed input means internal corruption.
Solutions
- Read the wrapped reason in the message and correct the URL: it must be ssh://[user@]host[:port]/path
- Ensure the path component is present — the caller requires it (ssh://host alone fails)
- If produced internally, inspect spawn_server_detached's URL building for dropped or truncated components
- Test parse_ssh_url_arg directly with the exact string to isolate which part fails
Example fix
// before fresh --ssh-url ssh://deploy@build-box // after fresh --ssh-url ssh://deploy@build-box/home/deploy/project
Defensive patterns
Strategy: validation
Validate before calling
fn looks_like_complete_ssh_url(url: &str) -> bool {
let rest = url.strip_prefix("ssh://").unwrap_or("");
let after_authority = rest.split('/').next().unwrap_or("");
!after_authority.split('@').last().unwrap_or("").is_empty()
&& url.contains('/')
} Try / catch
match parse_ssh_url_arg(url) {
Err(e) if e.to_string().starts_with("--ssh-url is not a valid") => {
eprintln!("{e:#}; expected ssh://[user@]host[:port]/path");
std::process::exit(2);
}
other => other?,
} Prevention
- Include the path component whenever generating ssh:// URLs — ssh://host alone is rejected
- Validate host[:port] syntax (no empty port after ':') before passing the URL along
- Unit-test parse_ssh_url_rest with missing host/port/path cases
- Keep spawn_server_detached's URL construction and the parser's expectations in sync
When it happens
Trigger: --ssh-url carries an ssh:// prefix but the remainder is unparseable: missing host (ssh:///path), invalid port (ssh://host:notaport/), or missing path when the caller requires one (ssh://host only).
Common situations: spawn_server_detached generating a URL with the path stripped; hand-testing the binary with ssh://host and no path; IPv6 hosts or usernames with characters the parser rejects.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- invalid ssh:// URL : . To open a local file with this name…
- Cannot open files from multiple remote hosts. First
- Cannot mix local and remote files. Use either local paths…
- --ssh-url expects an ssh:// URL, got
- Too many '+ ' arguments (at most one is allowed)
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/36096df094c12aa9.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-editor/src/main.rs:1458
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!(
"--ssh-url is not a valid ssh:// URL ({}): {:?}",
reason,
url
)
})
}
/// Parse a location that may be local, scp-style remote, or an
/// `ssh://` URL.
///
/// Accepted forms:
/// - local: `file`, `file:line`, `file:line:col`
/// - scp-style remote: `user@host:path[:line[:col]]`
/// - URL-style remote: `ssh://[user@]host[:port]/path[:line[:col]]`
///
/// When `ssh://` omits the user, the current login name (`$USER` /
/// `$USERNAME`) is used. The URL form is the only way to pass a
/// port. The path must be non-empty in both remote forms.View on GitHub (pinned to 67894ca546)