can1357/oh-my-pi · error
ssh:// does not support URL query strings; percent-encode a
Error message
ssh:// does not support URL query strings; percent-encode a literal '?' as %3F in the path: ${url.href} What it means
In a URL, `?` starts a query string, so the internal-URL parser strips anything after it from the path (`ssh://h/tmp/a?draft` parses with pathname `/tmp/a` and search `?draft`). Since ssh:// remote paths are pure filesystem paths with no query semantics, the handler rejects any URL containing a query string rather than silently reading the truncated path.
Source
Thrown at packages/coding-agent/src/internal-urls/ssh-protocol.ts:79
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
} catch {
return null;
}
}
/**
* 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",View on GitHub (pinned to 9690622007)
Solutions
- Percent-encode the literal `?` as `%3F` in the path: `ssh://host/tmp/a%3Fdraft`.
- Remove any query string you appended out of HTTP habit — ssh paths take no query parameters.
- Pass the intended remote path exactly; if you meant to select a different file, use that file's real path.
Example fix
// before
await readResource("ssh://host/tmp/report?draft");
// after
await readResource("ssh://host/tmp/report%3Fdraft"); Defensive patterns
Strategy: validation
Validate before calling
function assertNoSshQuery(url: string): void {
const u = new URL(url);
if (u.protocol === "ssh:" && u.search) {
throw new Error(`ssh:// URL must not contain a query string: ${url}`);
}
} Try / catch
try {
return await sshHandler.resolve(url);
} catch (err) {
if (err instanceof Error && err.message.includes("does not support URL query strings")) {
// retry with '?' encoded
return sshHandler.resolve(parseInternalUrl(url.replaceAll("?", "%3F")));
}
throw err;
} Prevention
- Treat ssh:// as a filesystem path, not an HTTP URL — no query strings or fragments.
- Always `encodeURIComponent()` path segments built from variables.
- Encode literal '?' as %3F in filenames.
- Never append cache-busting or version query params to ssh:// URLs.
When it happens
Trigger: `SshProtocolHandler.resolve()` or `.write()` (via `remotePathFromUrl`) with a `ssh://` URL whose `url.search` is non-empty, e.g. `ssh://host/tmp/a?draft`, or any path whose filename contains a literal `?` that was not percent-encoded as `%3F`.
Common situations: Files with `?` in their names (legal on POSIX) passed to ssh:// unencoded; URL templates appending `?version=...` or cache-busting params by habit from HTTP URLs; tooling auto-appending query parameters.
Related errors
- ssh:// does not support URL fragments; percent-encode a lite
- 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/986633a01fb68fc7.
Report an issue: GitHub.