can1357/oh-my-pi · error
Invalid xd:// URL: ${url.href}. Use xd:// or xd://<tool>.
Error message
Invalid xd:// URL: ${url.href}. Use xd:// or xd://<tool>. What it means
XdProtocolHandler.resolve() validates the incoming URL with parseXdUrl(), which accepts only "xd://" (root) or "xd://<tool>" with no path separators, query, or fragment characters. Anything else (wrong scheme, subpaths like xd://a/b, trailing /?# characters) makes the parser return null and the handler throws this error. It is pure URL-shape validation before any device access.
Source
Thrown at packages/coding-agent/src/internal-urls/xd-protocol.ts:34
return { name };
}
/** Whether a streaming path prefix could still become an `xd://` URL. */
export function couldBecomeXdUrl(partialPath: string): boolean {
if (partialPath.length <= XD_URL_PREFIX.length) {
return XD_URL_PREFIX.startsWith(partialPath.toLowerCase());
}
return partialPath.toLowerCase().startsWith(XD_URL_PREFIX);
}
/** Routes session-bound virtual tool devices through `xd://` URLs. */
export class XdProtocolHandler implements ProtocolHandler {
readonly scheme = "xd";
readonly immutable = true;
async resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
const target = parseXdUrl(url.href);
if (!target) throw new Error(`Invalid xd:// URL: ${url.href}. Use xd:// or xd://<tool>.`);
if (!context?.xd) throw new Error("xd:// is not mounted in this session.");
const content = await context.xd.read(target.name);
return { url: url.href, content, contentType: "text/plain", size: Buffer.byteLength(content) };
}
async write(url: InternalUrl, content: string, context?: WriteContext): Promise<void> {
const target = parseXdUrl(url.href);
if (!target) throw new Error(`Invalid xd:// URL: ${url.href}. Use xd://<tool>.`);
if (!context?.xd) throw new Error("xd:// is not mounted in this session.");
await context.xd.write(target.name, content);
}
}
View on GitHub (pinned to 9690622007)
Solutions
- Use "xd://" for the device root or "xd://<tool>" for a single tool device — no subpaths, query, or fragments.
- Pre-validate with parseXdUrl(url.href) and handle null before calling resolve.
- If you need hierarchical addressing, encode it in the tool name or use a different protocol.
Example fix
// before
await resolveInternalUrl("xd://browser/tab/1"); // path -> throws
// after
await resolveInternalUrl("xd://browser"); // valid device URL Defensive patterns
Strategy: validation
Validate before calling
import { parseXdUrl } from "./internal-urls/xd-protocol";
if (!parseXdUrl(url.href)) {
throw new Error(`Not a valid xd:// device URL: ${url.href}. Use xd:// or xd://<tool>.`);
} Type guard
function isXdDeviceUrl(href: string): boolean {
return /^xd:\/\/[^/?#]*$/.test(href.trim());
} Try / catch
try {
return await resolveInternalUrl(url);
} catch (err) {
if (err instanceof Error && err.message.startsWith("Invalid xd:// URL:")) {
// normalize: strip subpath/query, or route to the correct protocol handler
return;
}
throw err;
} Prevention
- Remember xd:// device names are flat: no '/', '?', or '#' allowed.
- Run parseXdUrl() on any externally supplied xd:// URL before resolving.
- Never treat xd:// like an HTTP URL with paths or query strings.
When it happens
Trigger: Resolving an InternalUrl whose href is not exactly xd:// or xd://<name> — e.g. "xd://browser/tab/1" (contains "/"), "xd://tool?x=1" (contains "?"), or a non-xd URL routed to this handler.
Common situations: Appending paths or query strings to xd:// URLs as if they were HTTP URLs; agents constructing hierarchical device addresses; routing a foreign URL into the xd handler.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- vault:// write only supports plain file paths
- vault:// URL requires a vault name or '_' for the active vau
- err.to_string() (timestamp parse error)
- duration is too large: {value}
- invalid date: {value}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/0aedcbf733657b35.
Report an issue: GitHub.