{"record":{"id":"161d0d727c625562","repo":"can1357/oh-my-pi","slug":"xd-is-not-mounted-in-this-session","errorCode":null,"errorMessage":"xd:// is not mounted in this session.","messagePattern":"xd:// is not mounted in this session\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/xd-protocol.ts","lineNumber":35,"sourceCode":"}\n\n/** Whether a streaming path prefix could still become an `xd://` URL. */\nexport function couldBecomeXdUrl(partialPath: string): boolean {\n\tif (partialPath.length <= XD_URL_PREFIX.length) {\n\t\treturn XD_URL_PREFIX.startsWith(partialPath.toLowerCase());\n\t}\n\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":17,"sourceCodeEnd":47,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/xd-protocol.ts#L17-L47","documentation":"XdProtocolHandler.resolve routes xd:// virtual tool-device URLs to a per-session 'xd' mount. The handler only has access to the device registry through ResolveContext.xd; when the session was created without an xd mount, the protocol cannot serve content, so resolve throws immediately after URL parsing succeeds. It signals that the URL scheme is recognized but the backing device layer is absent from this session.","triggerScenarios":"Calling resolve() with an xd:// (or xd://<tool>) InternalUrl while the ResolveContext passed in has no `xd` property — e.g. a session built without the tool-device mount, or a bare/incorrectly-constructed context object.","commonSituations":"Embedding the coding-agent SDK and forgetting to configure the xd mount in session options; resolving an xd:// URL captured from a previous session that had the mount; test harnesses invoking the protocol handler with a hand-built context; resolving a URL read from a persisted transcript in a new session that lacks the device.","solutions":["Create/enable the session's xd mount so ResolveContext.xd is populated, then retry the resolve","If the xd device is not needed, treat the URL as unresolvable in the caller and skip xd:// hrefs instead of resolving them","Check that the context object passed to resolve is the session's real resolve context, not a stale or default-built one"],"exampleFix":"// before\nawait handler.resolve(url, { /* no xd mount */ });\n// after\nconst ctx = session.getResolveContext(); // includes xd when the session mounts tool devices\nif (!ctx.xd) throw new Error('This session has no xd mount; cannot resolve ' + url.href);\nawait handler.resolve(url, ctx);","handlingStrategy":"validation","validationCode":"function canResolveXd(ctx: ResolveContext | undefined, href: string): boolean {\n  const target = parseXdUrl(href);\n  return target !== null && ctx?.xd !== undefined;\n}\n// call only if canResolveXd(ctx, url.href)","typeGuard":"function hasXdMount(ctx: ResolveContext | undefined): ctx is ResolveContext & { xd: NonNullable<ResolveContext['xd']> } {\n  return ctx?.xd !== undefined;\n}","tryCatchPattern":"try {\n  const resource = await handler.resolve(url, ctx);\n} catch (err) {\n  if (err instanceof Error && err.message === 'xd:// is not mounted in this session.') {\n    return null; // degrade gracefully: no xd device in this session\n  }\n  throw err;\n}","preventionTips":["Verify session options enable the xd mount before storing/resolving xd:// hrefs","Route URL resolution through a dispatcher that checks the scheme and available mounts first","When persisting transcripts, record which mounts the session had so future sessions can skip unavailable schemes","In SDK embeddings, always build the context from the session factory rather than hand-assembling it"],"tags":["protocol-handler","session-config","internal-urls"],"backgroundTag":"protocol-not-mounted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}