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

  1. Append an absolute path: `ssh://host/etc/hosts`.
  2. Use `ssh://host/` (trailing slash) to read the root directory listing.
  3. Check the code that constructs the URL — ensure the path variable is set and starts with `/`.
  4. 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

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


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