{"record":{"id":"65116f2ae9572a52","repo":"can1357/oh-my-pi","slug":"invalid-xd-url-url-href-use-xd-tool","errorCode":null,"errorMessage":"Invalid xd:// URL: ${url.href}. Use xd://<tool>.","messagePattern":"Invalid xd:// URL: (.+?)\\. Use xd://<tool>\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/xd-protocol.ts","lineNumber":42,"sourceCode":"\treturn partialPath.toLowerCase().startsWith(XD_URL_PREFIX);\n}\n\n/** Routes session-bound virtual tool devices through `xd://` URLs. */\nexport class XdProtocolHandler implements ProtocolHandler {\n\treadonly scheme = \"xd\";\n\treadonly immutable = true;\n\n\tasync resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {\n\t\tconst target = parseXdUrl(url.href);\n\t\tif (!target) throw new Error(`Invalid xd:// URL: ${url.href}. Use xd:// or xd://<tool>.`);\n\t\tif (!context?.xd) throw new Error(\"xd:// is not mounted in this session.\");\n\t\tconst content = await context.xd.read(target.name);\n\t\treturn { url: url.href, content, contentType: \"text/plain\", size: Buffer.byteLength(content) };\n\t}\n\n\tasync write(url: InternalUrl, content: string, context?: WriteContext): Promise<void> {\n\t\tconst target = parseXdUrl(url.href);\n\t\tif (!target) throw new Error(`Invalid xd:// URL: ${url.href}. Use xd://<tool>.`);\n\t\tif (!context?.xd) throw new Error(\"xd:// is not mounted in this session.\");\n\t\tawait context.xd.write(target.name, content);\n\t}\n}\n","sourceCodeStart":24,"sourceCodeEnd":47,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/xd-protocol.ts#L24-L47","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use a bare device name URL of the form xd://<tool> with no slashes, query, or fragment after the name","Verify the URL actually has the xd:// scheme before handing it to this handler (route other schemes to their own handlers)","Trim or sanitize the device name so stray characters like ? or # are removed"],"exampleFix":"// before\nawait handler.write({ href: 'xd://mytool/output?raw=1' } as InternalUrl, data, ctx); // throws\n// after\nawait handler.write({ href: 'xd://mytool' } as InternalUrl, data, ctx);","handlingStrategy":"validation","validationCode":"function isValidXdWriteUrl(href: string): boolean {\n  const target = parseXdUrl(href);\n  return target !== null && target.name !== null; // named device, root 'xd://' not writable\n}","typeGuard":null,"tryCatchPattern":"try {\n  await handler.write(url, content, ctx);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Invalid xd:// URL')) {\n    logger.warn('Rejected xd write URL', { href: url.href });\n    return;\n  }\n  throw err;\n}","preventionTips":["Always build xd URLs from the device name alone: `xd://${name}`","Never append paths, query strings, or fragments to xd:// URLs","Route only xd-scheme URLs to XdProtocolHandler; use a scheme dispatcher for everything else","Escape or reject device names containing /, ?, or # at the source (device registration)"],"tags":["url-validation","protocol-handler","internal-urls"],"backgroundTag":"invalid-url-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}