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

  1. Percent-encode the literal `?` as `%3F` in the path: `ssh://host/tmp/a%3Fdraft`.
  2. Remove any query string you appended out of HTTP habit — ssh paths take no query parameters.
  3. 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

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


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