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
- Remove the `:<password>` from the URL, keeping `ssh://user@host/path`
- Set up key-based auth (ssh-keygen + ssh-copy-id) or an ssh-agent for the host
- 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
- Never embed credentials in ssh:// URLs — rely on ~/.ssh/config or ssh.json keyPath entries
- Reuse ssh:// URLs derived from git remotes only after converting https:// credential URLs
- Lint URL templates that interpolate `${password}` for the ssh scheme
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- ssh reverse forward to ${config.sshTarget} exited with code
- SFTP password injection is unsupported by the shared SSH tra
- ssh://: port 0 is not a valid SSH port; use ssh://host:<1-65
- SSH key not found: ${keyPath}
- SSH key is not a file: ${keyPath}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d55a4abb41791b35.
Report an issue: GitHub.