can1357/oh-my-pi · error
ssh:// requires a host: ssh://<host>/<absolute-path>
Error message
ssh:// requires a host: ssh://<host>/<absolute-path>
What it means
The ssh:// scheme requires a host in the authority. If both WHATWG `hostname` and the raw decoded host are empty (e.g. `ssh:///etc/hosts` — a path with no host), `resolveTarget` throws this error, because there is no destination for OpenSSH to connect to. (A completely bare `ssh://` without a path is handled separately as the configured-hosts index.)
Source
Thrown at packages/coding-agent/src/internal-urls/ssh-protocol.ts:153
* an opaque OpenSSH destination so plain `~/.ssh/config` aliases work.
*/
async function resolveTarget(url: InternalUrl, cwd?: string): Promise<SSHConnectionTarget> {
// `parseInternalUrl` falls back to a lenient regex parse when WHATWG `new URL`
// rejects the input. For ssh:// that only happens on a malformed authority — an
// invalid or out-of-range port (`prod:abc`, `host:65536`) or a bad IPv6 literal —
// which would otherwise be mis-read as an opaque host and silently connect to the
// default port. Reject it before resolving.
if (!URL.canParse(url.href)) {
throw new Error(`ssh://: invalid host or port in "${url.href}"; use ssh://host[:1-65535]/<absolute-path>`);
}
// WHATWG `hostname` is bracketed only for a *valid* IPv6 literal, so a bracketed
// host is unambiguously IPv6 — hand OpenSSH the bare address. Percent-encoded
// bracketed aliases (e.g. `%5Bprod%3A2222%5D`) keep their literal brackets in the
// decoded `rawHost`, so they are matched and forwarded verbatim, never stripped.
const bareHost = url.hostname;
const rawAuthority = url.rawHost || bareHost;
if (!bareHost && !rawAuthority) {
throw new Error("ssh:// requires a host: ssh://<host>/<absolute-path>");
}
// `decodeOr` fails open, so a malformed percent-escape (`%ZZ`) in the authority
// would otherwise pass the canonical check below and reach OpenSSH literally.
// Reject it up front — the path decoder fails closed for the same bad escapes.
for (const part of [url.username, bareHost]) {
if (part.includes("%")) {
try {
decodeURIComponent(part);
} catch {
throw new Error(`ssh://: invalid percent-escape in authority "${url.href}"`);
}
}
}
if (url.password) {
throw new Error(
"ssh://: password authentication is not supported; ssh:// uses key/agent auth — drop the ':<password>' from the URL",
);
}View on GitHub (pinned to 9690622007)
Solutions
- Supply a host: `ssh://myhost/etc/hosts` (any destination OpenSSH can resolve, including `~/.ssh/config` aliases).
- Check the variable/config supplying the host — it is empty; set it before building the URL.
- Read bare `ssh://` (no path) to list configured hosts and pick the right one.
Example fix
// before await readResource(`ssh:///etc/hosts`); // missing host // after await readResource(`ssh://prod/etc/hosts`);
Defensive patterns
Strategy: validation
Validate before calling
function assertSshHostPresent(url: string): void {
const u = new URL(url);
if (u.protocol === "ssh:" && !u.hostname) {
throw new Error(`ssh:// URL requires a host: ${url}`);
}
} Type guard
function hasSshHost(url: URL): boolean {
return url.protocol === "ssh:" && url.hostname.length > 0;
} Try / catch
try {
return await sshHandler.resolve(url);
} catch (err) {
if (err instanceof Error && err.message.startsWith("ssh:// requires a host:")) {
// host variable was empty — list configured hosts for the user
return sshHandler.resolve(parseInternalUrl("ssh://"));
}
throw err;
} Prevention
- Check host config/variables are non-empty before building ssh:// URLs.
- Never hand-write ssh:///path forms; always prefix the host.
- Use bare ssh:// to discover available configured hosts.
When it happens
Trigger: `SshProtocolHandler.resolve()`/`.write()` (via `resolveTarget`) with a `ssh://` URL that has an empty authority but a non-empty path, e.g. `ssh:///etc/hosts` or `ssh://` built from an unset host variable.
Common situations: Template expansion where the host variable is empty or undefined (`ssh://${hostEnv}/path` with `hostEnv=""`); copy-pasting a URL and deleting the host by accident; config files where the host field is blank.
Related errors
- ssh:// requires an absolute path, e.g. ssh://host/etc/hosts
- ssh://: invalid host or port in "${url.href}"; use ssh://hos
- Provider delete URL must not embed an account credential
- ${destination} returned an invalid upload URL
- Destination option endpoint must be an absolute URL
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/ef472b4ec4f7905a.
Report an issue: GitHub.