can1357/oh-my-pi · error

Invalid memory glob URL: ${input}

Error message

Invalid memory glob URL: ${input}

What it means

splitMemoryGlobPattern first parses the input as scheme://authority followed by an optional /path, using a strict regex. Inputs that don't start with a valid scheme://authority — or are otherwise malformed as a URL — throw this error before any filesystem work happens.

Source

Thrown at packages/coding-agent/src/internal-urls/memory-protocol.ts:72

/**
 * Decode percent-escapes in a raw glob-suffix segment, bracket-escaping any
 * glob metacharacter that was percent-encoded so it stays a literal filename
 * character instead of becoming glob syntax.
 */
function decodeGlobSuffixSegment(rawSegment: string): string {
	// Escape runs are decoded together so multi-byte UTF-8 sequences survive.
	return rawSegment.replace(/(?:%[0-9a-f]{2})+/gi, run => decodeURIComponent(run).replace(/[*?[{]/g, "[$&]"));
}

/**
 * Split a memory:// glob at its first wildcard after validating the complete
 * decoded path. The suffix is validated before filesystem globbing so `..`
 * cannot escape a safely resolved base directory.
 */
export function splitMemoryGlobPattern(input: string): MemoryGlobPattern {
	const urlMatch = input.match(/^([a-z][a-z0-9+.-]*:\/\/[^/?#]*)(\/.*)?$/i);
	if (!urlMatch) {
		throw new Error(`Invalid memory glob URL: ${input}`);
	}

	// Parse only the scheme and authority. A literal `?` in the path is glob
	// syntax, not a query delimiter, and must survive unchanged.
	const url = parseInternalUrl(urlMatch[1]);
	const namespace = url.rawHost || url.hostname;
	if (url.protocol !== "memory:" || namespace !== MEMORY_NAMESPACE) {
		throw new Error(`Memory glob patterns require the ${MEMORY_NAMESPACE} namespace: ${input}`);
	}

	const rawPathname = urlMatch[2] ?? "";
	if (/%(?:2f|5c)/i.test(rawPathname)) {
		throw new Error(`Encoded path separators are not allowed in memory:// glob patterns: ${input}`);
	}

	let relativePath: string;
	try {
		relativePath = decodeURIComponent(rawPathname.replace(/^\//, ""));

View on GitHub (pinned to 9690622007)

Solutions

  1. Prefix the input with the full URL form: memory://root/<path-with-glob>, e.g. memory://root/notes/*.md.
  2. Validate the shape client-side with the same pattern (a scheme, ://, non-path authority, then /path) before calling.
  3. If input comes from an agent, correct the tool prompt/examples so memory globs are always fully qualified memory:// URLs.

Example fix

// before
const { baseUrl, globPattern } = splitMemoryGlobPattern("notes/*.md");
// after
const { baseUrl, globPattern } = splitMemoryGlobPattern("memory://root/notes/*.md");
Defensive patterns

Strategy: validation

Validate before calling

const URL_SHAPE = /^([a-z][a-z0-9+.-]*:\/\/[^/?#]*)(\/.*)?$/i;
function isParsableMemoryGlobUrl(input: string): boolean {
  return URL_SHAPE.test(input.trim());
}
// call splitMemoryGlobPattern only if isParsableMemoryGlobUrl(input)

Type guard

function looksLikeUrl(input: string): boolean {
  return /^[a-z][a-z0-9+.-]*:\/\//i.test(input);
}

Try / catch

let parsed;
try {
  parsed = splitMemoryGlobPattern(input);
} catch (e) {
  if (e instanceof Error && e.message.startsWith("Invalid memory glob URL")) {
    return splitMemoryGlobPattern(`memory://root/${input.replace(/^\/+/, "")}`); // repair missing scheme
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling memoryGlob/splitMemoryGlobPattern with a bare path like 'notes/*.md', a Windows-style path, a scheme-less glob, or a string with whitespace/characters that break the ^scheme://[^/?#]* structure.

Common situations: Hand-writing glob inputs and forgetting the memory://root prefix; passing a plain filesystem glob from config; an LLM emitting `memory:notes/*.md` or `memories/*.md` instead of a full URL; shell quoting stripping the scheme.

Related errors


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