can1357/oh-my-pi · error
ssh:// requires an absolute path, e.g. ssh://host/etc/hosts
Error message
ssh:// requires an absolute path, e.g. ssh://host/etc/hosts or ssh://host/ for the root directory
What it means
After rejecting invalid encodings, `remotePathFromUrl` requires the decoded path to be non-empty. An empty decoded pathname means no remote path was supplied; ssh:// always needs an absolute path (use `/` to address the root directory), so the handler throws this guidance error instead of guessing a default like the home directory.
Source
Thrown at packages/coding-agent/src/internal-urls/ssh-protocol.ts:96
if (url.search) {
throw new Error(
`ssh:// does not support URL query strings; percent-encode a literal '?' as %3F in the path: ${url.href}`,
);
}
if (url.hash) {
throw new Error(
`ssh:// does not support URL fragments; percent-encode a literal '#' as %23 in the path: ${url.href}`,
);
}
const raw = url.rawPathname ?? url.pathname;
let decoded: string;
try {
decoded = decodeURIComponent(raw);
} catch {
throw new Error(`Invalid URL encoding in ssh:// path: ${url.href}`);
}
if (!decoded) {
throw new Error(
"ssh:// requires an absolute path, e.g. ssh://host/etc/hosts or ssh://host/ for the root directory",
);
}
return decoded;
}
/** Load the configured SSH hosts from the `ssh` capability (managed/project `ssh.json`). */
async function loadConfiguredHosts(cwd?: string): Promise<SSHHost[]> {
const { items } = await capability.loadCapability<SSHHost>(sshCapability.id, cwd ? { cwd } : {});
return items;
}
/** One-line address for a host, e.g. `deploy@10.0.0.1:2222`. */
function hostAddress(host: SSHHost): string {
return `${host.username ? `${host.username}@` : ""}${host.host}${host.port ? `:${host.port}` : ""}`;
}
/** Render the configured-host index for a bare `ssh://` read (markdown with per-host links). */View on GitHub (pinned to 9690622007)
Solutions
- Append an absolute path: `ssh://host/etc/hosts`.
- Use `ssh://host/` (trailing slash) to read the root directory listing.
- Check the code that constructs the URL — ensure the path variable is set and starts with `/`.
- Use bare `ssh://` (no host) if you actually wanted the configured-hosts index.
Example fix
// before
await readResource(`ssh://${host}`); // no path
// after
await readResource(`ssh://${host}/etc/hosts`); Defensive patterns
Strategy: validation
Validate before calling
function assertSshPathPresent(url: string): void {
const u = new URL(url);
if (u.protocol === "ssh:" && u.hostname && (u.pathname === "" || u.pathname === undefined)) {
throw new Error(`ssh:// URL requires an absolute path: ${url}`);
}
} Try / catch
try {
return await sshHandler.resolve(url);
} catch (err) {
if (err instanceof Error && err.message.startsWith("ssh:// requires an absolute path")) {
// default to root listing
return sshHandler.resolve(parseInternalUrl(`ssh://${host}/`));
}
throw err;
} Prevention
- Always include a trailing '/' or an absolute path in ssh:// URLs.
- Check that path template variables are non-empty before interpolation.
- Use bare ssh:// (no host) when you want the configured-host index, not ssh://host with no path.
When it happens
Trigger: `SshProtocolHandler.resolve()`/`.write()` (via `remotePathFromUrl`) with a `ssh://` URL whose pathname decodes to an empty string, e.g. `ssh://host` with no trailing path at all (a bare `ssh://` host index is handled elsewhere only when there is no host).
Common situations: Building URLs from template variables where the path part was empty/undefined; dropping the trailing `/` when reading the remote root; URL sanitizers that stripped the path entirely.
Related errors
- ssh://: invalid host or port in "${url.href}"; use ssh://hos
- ssh:// requires a host: ssh://<host>/<absolute-path>
- 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/a3dff5a113a31cd6.
Report an issue: GitHub.