can1357/oh-my-pi · error

ssh://: password authentication is not supported; ssh:// use

Error message

ssh://: password authentication is not supported; ssh:// uses key/agent auth — drop the ':<password>' from the URL

What it means

The ssh:// protocol handler refuses any URL that embeds a password (`ssh://user:pass@host/path`). SSH authenticates with keys or an agent, never a URL password, so embedding one indicates the author ported an https://-style URL. The handler throws immediately during target resolution, before any connection is attempted.

Source

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

	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",
		);
	}
	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.

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove the `:<password>` from the URL, keeping `ssh://user@host/path`
  2. Set up key-based auth (ssh-keygen + ssh-copy-id) or an ssh-agent for the host
  3. Add the host to ~/.ssh/config or the project's ssh.json capability with keyPath instead of embedding credentials

Example fix

// before
resolve('ssh://deploy:hunter2@prod.example.com/etc/hosts')
// after
resolve('ssh://deploy@prod.example.com/etc/hosts')
Defensive patterns

Strategy: validation

Validate before calling

const u = new URL(candidate);
if (u.protocol === 'ssh:' && u.password) throw new Error(`strip ':<password>' from ${candidate}; ssh uses key/agent auth`);

Type guard

function hasNoSshPassword(u: URL): boolean { return u.protocol !== 'ssh:' || !u.password; }

Try / catch

try {
  const res = await handler.resolve(url, ctx);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('ssh://: password authentication is not supported')) {
    // rebuild URL without credentials and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a URL whose WHATWG parse yields a non-empty `url.password`, e.g. `ssh://deploy:secret@prod.example.com/etc/hosts` — typically a paste from an HTTPS git URL or a database connection string.

Common situations: Converting an `https://user:token@host/repo` URL to ssh://; pasting credentials from a legacy tool that supported password auth; mistaking ssh:// for a scheme that accepts inline credentials.

Understand the failure class

Related errors


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