{"record":{"id":"36096df094c12aa9","repo":"sinelaw/fresh","slug":"ssh-url-is-not-a-valid-ssh-url","errorCode":null,"errorMessage":"--ssh-url is not a valid ssh:// URL ({}): {:?}","messagePattern":"--ssh-url is not a valid ssh:// URL \\((.+?)\\): (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-editor/src/main.rs","lineNumber":1458,"sourceCode":"        anyhow::bail!(\n            \"Cannot mix local and remote files. Use either local paths or remote paths (ssh:// or user@host:path).\"\n        );\n    }\n\n    Ok(Some(remote_location_to_ssh_url(first)))\n}\n\n/// Parse a standalone `ssh://…` URL passed via the internal\n/// `--ssh-url` flag.  Accepts only the URL form (not scp-style) and\n/// the URL must carry a path; anything else is a hard error because\n/// this input comes from our own `spawn_server_detached` and a\n/// malformed URL there means we corrupted it on the way over.\nfn parse_ssh_url_arg(url: &str) -> AnyhowResult<RemoteLocation> {\n    let rest = url\n        .strip_prefix(\"ssh://\")\n        .ok_or_else(|| anyhow::anyhow!(\"--ssh-url expects an ssh:// URL, got {:?}\", url))?;\n    parse_ssh_url_rest(rest, default_ssh_user().as_deref()).map_err(|reason| {\n        anyhow::anyhow!(\n            \"--ssh-url is not a valid ssh:// URL ({}): {:?}\",\n            reason,\n            url\n        )\n    })\n}\n\n/// Parse a location that may be local, scp-style remote, or an\n/// `ssh://` URL.\n///\n/// Accepted forms:\n/// - local: `file`, `file:line`, `file:line:col`\n/// - scp-style remote: `user@host:path[:line[:col]]`\n/// - URL-style remote: `ssh://[user@]host[:port]/path[:line[:col]]`\n///\n/// When `ssh://` omits the user, the current login name (`$USER` /\n/// `$USERNAME`) is used.  The URL form is the only way to pass a\n/// port.  The path must be non-empty in both remote forms.","sourceCodeStart":1440,"sourceCodeEnd":1476,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/src/main.rs#L1440-L1476","documentation":"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.","triggerScenarios":"--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).","commonSituations":"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.","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"],"exampleFix":"// before\nfresh --ssh-url ssh://deploy@build-box\n// after\nfresh --ssh-url ssh://deploy@build-box/home/deploy/project","handlingStrategy":"validation","validationCode":"fn looks_like_complete_ssh_url(url: &str) -> bool {\n    let rest = url.strip_prefix(\"ssh://\").unwrap_or(\"\");\n    let after_authority = rest.split('/').next().unwrap_or(\"\");\n    !after_authority.split('@').last().unwrap_or(\"\").is_empty()\n        && url.contains('/')\n}","typeGuard":null,"tryCatchPattern":"match parse_ssh_url_arg(url) {\n    Err(e) if e.to_string().starts_with(\"--ssh-url is not a valid\") => {\n        eprintln!(\"{e:#}; expected ssh://[user@]host[:port]/path\");\n        std::process::exit(2);\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["ssh","url-parsing","cli"],"backgroundTag":"invalid-url-format","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}