{"record":{"id":"a3dff5a113a31cd6","repo":"can1357/oh-my-pi","slug":"ssh-requires-an-absolute-path-e-g-ssh-host","errorCode":null,"errorMessage":"ssh:// requires an absolute path, e.g. ssh://host/etc/hosts or ssh://host/ for the root directory","messagePattern":"ssh:// requires an absolute path, e\\.g\\. ssh://host/etc/hosts or ssh://host/ for the root directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/ssh-protocol.ts","lineNumber":96,"sourceCode":"\tif (url.search) {\n\t\tthrow new Error(\n\t\t\t`ssh:// does not support URL query strings; percent-encode a literal '?' as %3F in the path: ${url.href}`,\n\t\t);\n\t}\n\tif (url.hash) {\n\t\tthrow new Error(\n\t\t\t`ssh:// does not support URL fragments; percent-encode a literal '#' as %23 in the path: ${url.href}`,\n\t\t);\n\t}\n\tconst raw = url.rawPathname ?? url.pathname;\n\tlet decoded: string;\n\ttry {\n\t\tdecoded = decodeURIComponent(raw);\n\t} catch {\n\t\tthrow new Error(`Invalid URL encoding in ssh:// path: ${url.href}`);\n\t}\n\tif (!decoded) {\n\t\tthrow new Error(\n\t\t\t\"ssh:// requires an absolute path, e.g. ssh://host/etc/hosts or ssh://host/ for the root directory\",\n\t\t);\n\t}\n\treturn decoded;\n}\n\n/** Load the configured SSH hosts from the `ssh` capability (managed/project `ssh.json`). */\nasync function loadConfiguredHosts(cwd?: string): Promise<SSHHost[]> {\n\tconst { items } = await capability.loadCapability<SSHHost>(sshCapability.id, cwd ? { cwd } : {});\n\treturn items;\n}\n\n/** One-line address for a host, e.g. `deploy@10.0.0.1:2222`. */\nfunction hostAddress(host: SSHHost): string {\n\treturn `${host.username ? `${host.username}@` : \"\"}${host.host}${host.port ? `:${host.port}` : \"\"}`;\n}\n\n/** Render the configured-host index for a bare `ssh://` read (markdown with per-host links). */","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/ssh-protocol.ts#L78-L114","documentation":"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.","triggerScenarios":"`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).","commonSituations":"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.","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."],"exampleFix":"// before\nawait readResource(`ssh://${host}`); // no path\n// after\nawait readResource(`ssh://${host}/etc/hosts`);","handlingStrategy":"validation","validationCode":"function assertSshPathPresent(url: string): void {\n  const u = new URL(url);\n  if (u.protocol === \"ssh:\" && u.hostname && (u.pathname === \"\" || u.pathname === undefined)) {\n    throw new Error(`ssh:// URL requires an absolute path: ${url}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await sshHandler.resolve(url);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"ssh:// requires an absolute path\")) {\n    // default to root listing\n    return sshHandler.resolve(parseInternalUrl(`ssh://${host}/`));\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["url","ssh-protocol","validation"],"backgroundTag":"invalid-url-syntax","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}