can1357/oh-my-pi · error
ssh:// does not support URL fragments; percent-encode a lite
Error message
ssh:// does not support URL fragments; percent-encode a literal '#' as %23 in the path: ${url.href} What it means
In a URL, `#` starts a fragment and the parser strips it and everything after it from the path. ssh:// remote paths have no fragment semantics, so the handler rejects any URL containing a fragment instead of silently operating on the truncated path; a literal `#` in a remote filename must be percent-encoded as `%23`.
Source
Thrown at packages/coding-agent/src/internal-urls/ssh-protocol.ts:84
/**
* Remote absolute path from the URL. Uses `rawPathname` (pre-normalization) so
* `..`/`//` and percent-escapes survive verbatim to the remote shell; the
* authority (host/user/port) stays on the WHATWG fields, which preserve case for
* the non-special `ssh` scheme.
*/
function remotePathFromUrl(url: InternalUrl): string {
// `?`/`#` are URL delimiters, so parseInternalUrl strips them from the path
// (`ssh://h/tmp/a?draft` → `/tmp/a`). Reject the unsupported suffix instead of
// silently operating on the truncated path; a literal `?`/`#` in a filename
// must be percent-encoded (`%3F`/`%23`).
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;
}
View on GitHub (pinned to 9690622007)
Solutions
- Percent-encode the literal `#` as `%23`: `ssh://host/etc/config%23backup`.
- Strip any fragment you appended for HTTP-style anchors — it has no meaning for ssh paths.
- Rename the remote file to avoid `#` if encoding is inconvenient in your tooling.
Example fix
// before
await readResource("ssh://host/docs/notes#2.txt");
// after
await readResource("ssh://host/docs/notes%232.txt"); Defensive patterns
Strategy: validation
Validate before calling
function assertNoSshFragment(url: string): void {
const u = new URL(url);
if (u.protocol === "ssh:" && u.hash) {
throw new Error(`ssh:// URL must not contain a fragment: ${url}`);
}
} Try / catch
try {
return await sshHandler.resolve(url);
} catch (err) {
if (err instanceof Error && err.message.includes("does not support URL fragments")) {
return sshHandler.resolve(parseInternalUrl(url.replaceAll("#", "%23")));
}
throw err;
} Prevention
- Encode literal '#' as %23 in ssh:// paths.
- Strip markdown-style anchors before passing paths to ssh://.
- Always percent-encode path segments built from variables.
- Remember '#-containing filenames are valid on POSIX remote hosts.
When it happens
Trigger: `SshProtocolHandler.resolve()` or `.write()` (via `remotePathFromUrl`) with a `ssh://` URL whose `url.hash` is non-empty, e.g. `ssh://host/etc/issue#1` or a filename like `notes#2.txt` passed unencoded.
Common situations: POSIX filenames containing `#` (common for scratch/versioned files like `config#backup`) used in ssh:// URLs; markdown-style anchor fragments appended by habit; copy-pasting paths containing `#` without encoding.
Related errors
- ssh:// does not support URL query strings; percent-encode a
- Invalid URL encoding in ssh:// path: ${url.href}
- ssh://: invalid percent-escape in authority "${url.href}"
- Invalid URL encoding in memory:// path: ${url.href}
- ssh:// requires an absolute path, e.g. ssh://host/etc/hosts
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f8ec1ba93af3a19a.
Report an issue: GitHub.