can1357/oh-my-pi · error

Invalid xd:// URL: ${url.href}. Use xd://<tool>.

Error message

Invalid xd:// URL: ${url.href}. Use xd://<tool>.

What it means

XdProtocolHandler.write validates the target URL before delegating to the session's xd device writer. parseXdUrl returns null for anything that is not an xd:// URL or that contains path/query/fragment characters (/, ?, #), so write throws with guidance to use a plain device URL. Note the root form 'xd://' (empty name) is valid for resolve but this message steers writers toward xd://<tool> because writing to the root device is not meaningful.

Source

Thrown at packages/coding-agent/src/internal-urls/xd-protocol.ts:42

	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

  1. Use a bare device name URL of the form xd://<tool> with no slashes, query, or fragment after the name
  2. Verify the URL actually has the xd:// scheme before handing it to this handler (route other schemes to their own handlers)
  3. Trim or sanitize the device name so stray characters like ? or # are removed

Example fix

// before
await handler.write({ href: 'xd://mytool/output?raw=1' } as InternalUrl, data, ctx); // throws
// after
await handler.write({ href: 'xd://mytool' } as InternalUrl, data, ctx);
Defensive patterns

Strategy: validation

Validate before calling

function isValidXdWriteUrl(href: string): boolean {
  const target = parseXdUrl(href);
  return target !== null && target.name !== null; // named device, root 'xd://' not writable
}

Try / catch

try {
  await handler.write(url, content, ctx);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Invalid xd:// URL')) {
    logger.warn('Rejected xd write URL', { href: url.href });
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling write() with url.href that is not an xd:// URL at all (different scheme, leading whitespace is trimmed so that is OK), or an xd:// URL containing /, ?, or # — e.g. xd://tool/subpath, xd://tool?key=1, xd://tool#frag.

Common situations: Passing a regular file:// or https:// URL to the xd handler by routing mistake; appending a path or query string to an xd:// URL out of habit from http URLs; constructing the URL by string concatenation that leaves a trailing slash or fragment.

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


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