can1357/oh-my-pi · error

ssh://: port 0 is not a valid SSH port; use ssh://host:<1-65

Error message

ssh://: port 0 is not a valid SSH port; use ssh://host:<1-65535>/<path> or omit the port

What it means

Port 0 is a reserved placeholder meaning 'pick an ephemeral port' and is never a valid destination port for sshd. The handler rejects it explicitly during authority resolution so the connection is not attempted against port 0, which would always fail.

Source

Thrown at packages/coding-agent/src/internal-urls/ssh-protocol.ts:177

		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",
		);
	}
	const isIpv6Literal = bareHost.startsWith("[") && bareHost.endsWith("]");
	const sshHost = isIpv6Literal ? bareHost.slice(1, -1) : bareHost;
	const username = url.username || undefined;
	const port = url.port ? Number(url.port) : undefined;
	if (port === 0) {
		throw new Error("ssh://: port 0 is not a valid SSH port; use ssh://host:<1-65535>/<path> or omit the port");
	}
	// An empty port (`ssh://prod:/path`, `ssh://user@host:/path`, including
	// percent-encoded authority parts) parses cleanly with `url.port === ""`, so it
	// slips past the malformed-authority guard and would be read as "no port" —
	// silently using the default/configured target. `url.rawHost` is the decoded
	// authority and uniquely retains the trailing `:`; comparing it to the decoded
	// host (+ user) catches the empty port, while a percent-encoded alias like
	// `prod%3A` (whose decoded host already ends in `:`) reconstructs to `prod::`
	// and is left alone.
	const decodeOr = (s: string): string => {
		try {
			return decodeURIComponent(s);
		} catch {
			return s;
		}
	};
	if (port === undefined && url.rawHost === `${username ? `${decodeOr(username)}@` : ""}${decodeOr(bareHost)}:`) {
		throw new Error(`ssh://: empty port in "${url.href}"; use ssh://host:<1-65535>/<path> or drop the colon`);

View on GitHub (pinned to 9690622007)

Solutions

  1. Replace port 0 with the real SSH port (commonly 22 or a custom value like 2222)
  2. Drop the `:0` suffix entirely to use the default/configured port
  3. Check the upstream config/source that emitted the port to fix it where it is generated

Example fix

// before
resolve('ssh://prod.example.com:0/etc/hosts')
// after
resolve('ssh://prod.example.com:22/etc/hosts')
Defensive patterns

Strategy: validation

Validate before calling

const u = new URL(candidate);
if (u.protocol === 'ssh:' && u.port !== '' && Number(u.port) === 0) throw new Error('ssh port must be 1-65535; drop :0 or fix the port');

Type guard

function hasValidSshPort(u: URL): boolean { const p = Number(u.port); return u.port === '' || (Number.isInteger(p) && p >= 1 && p <= 65535); }

Try / catch

try {
  const res = await handler.resolve(url, ctx);
} catch (e) {
  if (e instanceof Error && e.message.includes('port 0 is not a valid SSH port')) {
    // omit or correct the port, then retry
  } else throw e;
}

Prevention

When it happens

Trigger: Resolving an ssh:// URL whose parsed port numerically equals 0, e.g. `ssh://prod.example.com:0/etc/hosts` (produced by a variable that evaluated to 0 or a template with a missing port value).

Common situations: A config generator substituting a falsy/missing port variable as 0; copy-pasted IPv6 or service entry where the port field was zeroed; hand-written URLs with an accidental 0.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/eb1a4c40fe2bd4f7. Report an issue: GitHub.