can1357/oh-my-pi · error
memory:// URL escapes memory root
Error message
memory:// URL escapes memory root
What it means
memory:// URLs may only address paths inside the configured memory root directory. ensureWithinRoot checks the target (and, in tryResolveInRoot, its symlink-resolved realpath) against the root prefix and throws if the resolved path would land outside it. This is a path-traversal guard: the URL's decoded path, or a symlink inside memory, pointed outside the sanctioned root.
Source
Thrown at packages/coding-agent/src/internal-urls/memory-protocol.ts:40
const agentDir = getAgentDir();
const roots: string[] = [];
for (const ref of AgentRegistry.global().list()) {
const sm = ref.session?.sessionManager;
if (!sm) continue;
const root = getMemoryRoot(agentDir, sm.getCwd());
if (root && !roots.includes(root)) roots.push(root);
}
return roots;
}
function memoryRootsForContext(context?: ResolveContext): string[] {
if (context?.cwd) return [getMemoryRoot(getAgentDir(), context.cwd)];
return memoryRootsFromRegistry();
}
function ensureWithinRoot(targetPath: string, rootPath: string): void {
if (targetPath !== rootPath && !targetPath.startsWith(`${rootPath}${path.sep}`)) {
throw new Error("memory:// URL escapes memory root");
}
}
function toMemoryValidationError(error: unknown): Error {
const message = error instanceof Error ? error.message : String(error);
return new Error(message.replace("skill://", "memory://"));
}
export interface MemoryGlobPattern {
baseUrl: string;
globPattern: string;
}
/**
* 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.
*/View on GitHub (pinned to 9690622007)
Solutions
- Replace any symlink inside the memory root with a real copy of the target file so realpath stays under the root.
- Decode the URL and confirm the path contains no ../ or absolute components; use a plain relative path under memory://root/.
- Check where getMemoryRoot places the root for your session cwd and keep target files inside that directory.
- If you need content from outside memory, read it directly by file path instead of through memory://.
Example fix
# before — symlink escapes the memory root ln -s ~/docs/notes.md .omp/memories/notes.md # after — real file inside the root cp ~/docs/notes.md .omp/memories/notes.md && rm .omp/memories/notes.md.orig
Defensive patterns
Strategy: validation
Validate before calling
import * as path from "node:path";
import * as fs from "node:fs/promises";
const root = await fs.realpath(memoryRoot);
const target = path.resolve(root, decodeURIComponent(urlPath.replace(/^\//, "")));
const real = await fs.realpath(target).catch(() => target);
if (real !== root && !real.startsWith(root + path.sep)) {
throw new Error(`refusing: ${urlPath} escapes memory root`);
} Type guard
function isWithinRoot(targetPath: string, rootPath: string): boolean {
return targetPath === rootPath || targetPath.startsWith(`${rootPath}${path.sep}`);
} Try / catch
try {
return await handler.resolve(url, context);
} catch (e) {
if (e instanceof Error && e.message === "memory:// URL escapes memory root") {
logger.warn("memory:// traversal blocked", { href: url.href });
return null;
}
throw e;
} Prevention
- Never place symlinks pointing outside the memory root directory.
- Keep memory:// paths plain and relative — no ../ segments or absolute components.
- After moving or copying a memory directory, audit it for stale symlinks.
- Treat memory:// URLs from agent output as untrusted input and validate before reading.
When it happens
Trigger: A memory:// URL whose decoded path escapes via ../ segments that slip past validateRelativePath, or more commonly a symlink inside the memory root pointing to a file elsewhere on disk — the final fs.realpath check fails the prefix test and throws.
Common situations: Users symlinking memory files to notes elsewhere in their repo; project setups where .omp/memories contains links into shared drives; crafted memory:// URLs in agent output attempting traversal; copying a memory dir that contains stale symlinks after a move.
Related errors
- Shared-folder destination escapes its configured root
- vault:// URL escapes vault root
- The managed-skills root is a symlink; refusing to operate ou
- Managed skill "${name}" SKILL.md is a symlink; refusing to o
- Managed skill "${name}" resolves through a symlink; refusing
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/17f84692eb1b46b7.
Report an issue: GitHub.