can1357/oh-my-pi · error
ssh:// requires a host before the path: ssh://<host>${rawPat
Error message
ssh:// requires a host before the path: ssh://<host>${rawPath} (host-less ssh://${rawPath} is not valid) What it means
Bare `ssh://` (no host) resolves to an index of configured hosts, but `ssh:///etc/hosts` — no host AND a path — is ambiguous: honoring the path would require a host to connect to. The handler rejects it rather than silently dropping the path and returning the host list.
Source
Thrown at packages/coding-agent/src/internal-urls/ssh-protocol.ts:268
/** Format a one-level remote directory listing — mirrors buildDirectoryResource's plain `name/` lines. */
function formatDirListing(entries: readonly RemoteDirEntry[]): string {
if (entries.length === 0) return "(empty directory)";
return entries.map(entry => `${entry.name}${entry.isDirectory ? "/" : ""}`).join("\n");
}
export class SshProtocolHandler implements ProtocolHandler {
readonly scheme = "ssh";
readonly immutable = false;
async resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
// Bare `ssh://` (or `ssh:///`) with no host lists the configured hosts. A
// host-less URL that still carries a path (`ssh:///etc/hosts`) is malformed —
// reject it instead of silently dropping the path and listing hosts.
if (!(url.rawHost || url.hostname)) {
const rawPath = url.rawPathname ?? url.pathname;
if (rawPath && rawPath !== "/") {
throw new Error(
`ssh:// requires a host before the path: ssh://<host>${rawPath} (host-less ssh://${rawPath} is not valid)`,
);
}
return this.#resolveHostIndex(url, context?.cwd);
}
const target = await resolveTarget(url, context?.cwd);
const remotePath = remotePathFromUrl(url);
// Classify before reading. A FIFO with no writer would block `head` until the
// timeout, and a device (e.g. /dev/zero) would stream the whole probe, so a
// special file must fail fast. Only a regular file is read; a directory lists.
// `missing`/stat-failure falls through to the read so its original remote stderr
// (e.g. "No such file or directory") still surfaces.
let kind: RemotePathKind | undefined;
try {
kind = await statRemotePath(target, remotePath, { signal: context?.signal });
} catch {
// stat failed (host/connection issue) — fall through; the read gives a clearer error.
}View on GitHub (pinned to 9690622007)
Solutions
- Insert the host before the path: `ssh://prod/etc/hosts`
- If you meant the host index, read bare `ssh://` without a path
- Fix the template so the host variable is never empty
Example fix
// before
resolve('ssh:///etc/hosts')
// after
resolve('ssh://prod.example.com/etc/hosts') Defensive patterns
Strategy: validation
Validate before calling
const u = new URL(candidate);
if (u.protocol === 'ssh:' && !u.hostname && u.pathname && u.pathname !== '/') throw new Error(`missing host before path: use ssh://<host>${u.pathname}`); Type guard
function hasSshHost(u: URL): boolean { return Boolean(u.hostname || (u.rawHost ?? '')); } Try / catch
try {
const res = await handler.resolve(url, ctx);
} catch (e) {
if (e instanceof Error && e.message.includes('requires a host before the path')) {
// insert the host segment, then retry
} else throw e;
} Prevention
- Never interpolate an empty host variable into ssh://${host}${path} templates
- Read bare `ssh://` only when you want the configured-host index
- Keep host and path in separate, individually validated variables
When it happens
Trigger: Resolving an ssh:// URL with both `rawHost` and `hostname` empty while the raw pathname is present and not `/`, e.g. `ssh:///etc/hosts` (missing host segment after the double slash).
Common situations: Dropping the host when editing a URL; a template `ssh://${host}${path}` with an empty host variable; typo leaving only two slashes.
Related errors
- ssh://: empty port in "${url.href}"; use ssh://host:<1-65535
- ssh://: empty username in "${url.href}"; drop the leading '@
- ssh://: unsupported or malformed authority in "${url.href}";
- imageUrls exposure "ssh" requires imageUrls.publicBaseUrl
- imageUrls exposure "ssh" requires imageUrls.sshTarget
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/5a58e3330914794c.
Report an issue: GitHub.