sinelaw/fresh · error

invalid ssh:// URL : . To open a local file with this name…

Error message

invalid ssh:// URL {:?}: {}. To open a local file with this name, prefix it with ./

What it means

parse_location accepts user input for a location to open. When the input starts with ssh:// but the rest fails to parse, it converts the parse reason into this error and helpfully suggests that if the user actually meant a local file literally named like a URL, they should prefix the path with ./ to force local interpretation.

Solutions

  1. Fix the URL syntax: ssh://[user@]host[:port]/path with a valid host and port
  2. If you meant a local file that literally starts with ssh://, open it as ./ssh://... per the error hint
  3. Drop scp-style colon paths; use the slash-separated ssh URL form instead
  4. Verify user/host characters are URL-safe (escape or quote special characters)

Example fix

// before
fresh ssh://server:22:~/project
// after
fresh ssh://user@server:22/home/user/project
Defensive patterns

Strategy: validation

Validate before calling

fn parse_or_local(input: &str) -> Result<ParsedLocation, String> {
    if input.starts_with("ssh://") && !input.contains("@") && !input.contains(":") {
        return Err(format!("invalid ssh URL '{input}'; use ./{} for a local file", input));
    }
    Ok(ParsedLocation::Local(input.into()))
}

Try / catch

match parse_location(input, default_user) {
    Err(e) if e.to_string().contains("invalid ssh:// URL") => {
        eprintln!("{e:#}"); // hint already suggests ./ prefix for local files
    }
    other => other?,
}

Prevention

When it happens

Trigger: A user passes an argument like ssh://bad host form on the command line or in the open prompt: missing host, bad port, or malformed user@host — anything parse_ssh_url_rest rejects.

Common situations: Typing ssh://host: (trailing colon, empty port); copying an scp-style shorthand (ssh://host:path with colon-separated path) the parser doesn't accept; intentionally opening a file named 'ssh://...' in the working directory without ./ prefix.

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


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

Appendix: source

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

/// argument is a hard error, never a local-path fallback.  Silently
/// opening a local buffer named `ssh://host/...` hid the failure and
/// invited saving to a bogus local path (#2221).  A genuine local
/// file whose name starts with `ssh://` can still be opened as
/// `./ssh://...`.
fn parse_location(input: &str) -> AnyhowResult<ParsedLocation> {
    parse_location_with_default_user(input, default_ssh_user().as_deref())
}

/// `parse_location` with the `$USER`/`$USERNAME` fallback injected,
/// so tests can pin it without mutating the process environment.
fn parse_location_with_default_user(
    input: &str,
    default_user: Option<&str>,
) -> AnyhowResult<ParsedLocation> {
    if let Some(rest) = input.strip_prefix("ssh://") {
        return match parse_ssh_url_rest(rest, default_user) {
            Ok(loc) => Ok(ParsedLocation::Remote(loc)),
            Err(reason) => Err(anyhow::anyhow!(
                "invalid ssh:// URL {:?}: {}. \
                 To open a local file with this name, prefix it with ./",
                input,
                reason
            )),
        };
    }

    // scp-style: user@host:path. Must have @ before the first : to
    // count as remote (skips Windows drive letters like `C:\...`).
    if let Some(at_pos) = input.find('@') {
        let user = &input[..at_pos];
        let after_at = &input[at_pos + 1..];

        if let Some(colon_pos) = after_at.find(':') {
            let host = &after_at[..colon_pos];
            let path_and_rest = &after_at[colon_pos + 1..];

View on GitHub (pinned to 67894ca546)