can1357/oh-my-pi · error · ToolError

e instanceof Error ? e.message : String(e) (wraps parseInter

Error message

e instanceof Error ? e.message : String(e) (wraps parseInternalUrl error)

What it means

Before resolving an agent:// (or other internal) URL with query extraction, ReadTool parses it via parseInternalUrl. Malformed URLs (bad scheme shape, unparseable host/colons) throw inside parseInternalUrl and are re-wrapped as a ToolError carrying the underlying message.

Source

Thrown at packages/coding-agent/src/tools/read.ts:2188

	/**
	 * Handle internal URLs (agent://, artifact://, memory://, skill://, rule://, local://, mcp://).
	 * Supports pagination via offset/limit but rejects them when query extraction is used.
	 */
	async #handleInternalUrl(
		url: string,
		parsedSel: ParsedSelector,
		signal?: AbortSignal,
	): Promise<AgentToolResult<ReadToolDetails>> {
		const internalRouter = InternalUrlRouter.instance();

		// Check if URL has query extraction (agent:// only).
		// Use parseInternalUrl which handles colons in host (namespaced skills).
		let urlMeta: InternalUrl;
		try {
			urlMeta = parseInternalUrl(url);
		} catch (e) {
			throw new ToolError(e instanceof Error ? e.message : String(e));
		}
		const scheme = urlMeta.protocol.replace(/:$/, "").toLowerCase();
		let hasExtraction = false;
		if (scheme === "agent") {
			const hasPathExtraction = urlMeta.pathname && urlMeta.pathname !== "/" && urlMeta.pathname !== "";
			const queryParam = urlMeta.searchParams.get("q");
			const hasQueryExtraction = queryParam !== null && queryParam !== "";
			hasExtraction = hasPathExtraction || hasQueryExtraction;
		}
		if (scheme === "artifact") {
			return this.#readArtifactFile(urlMeta, parsedSel, signal);
		}

		// local:// files are real on-disk paths. Detect image files and emit a
		// decoded image block before the text-only resource contract UTF-8
		// decodes the binary into mojibake. The fast path returns null for
		// non-images, directories, listings, or any resolution failure, so the
		// text path below reproduces the router's not-found / symlink-escape

View on GitHub (pinned to 9690622007)

Solutions

  1. Fix the URL structure: scheme://host/path with query params for extraction (e.g. agent://skill?q=...).
  2. For skills with colons in names, follow the namespaced-host form supported by parseInternalUrl.
  3. Validate the URL with new URL(...) or the same parser before calling read.

Example fix

// before
read("agent://my:skill/doc")
// after
read("agent://my.skill/doc")  // or namespaced host form accepted by parseInternalUrl
Defensive patterns

Strategy: validation

Validate before calling

try { new URL(url.replace(/^agent:/, 'https:')); } catch { throw new Error('Malformed internal URL: ' + url); }

Type guard

function isParsableInternalUrl(url) { try { parseInternalUrl(url); return true; } catch { return false; } }

Try / catch

try { return await read(url) } catch (e) { if (e instanceof ToolError && !('Invalid selector' in e.message)) { /* log and rebuild URL from parts */ } throw e; }

Prevention

When it happens

Trigger: Reading a URL like 'agent://::bad' or a URL whose host/path structure parseInternalUrl cannot parse — e.g. missing host, illegal colon placement in namespaced skill urls.

Common situations: Agent constructs resource URLs programmatically and mis-joins segments; skill names containing colons are not escaped per the namespaced-host convention; copy-pasted URLs mangled.

Related errors


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