{"record":{"id":"1614e9b9eea18da4","repo":"shareAI-lab/learn-claude-code","slug":"memory-directory-escapes-the-workspace","errorCode":null,"errorMessage":"Memory directory escapes the workspace","messagePattern":"Memory directory escapes the workspace","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s09_memory/code.py","lineNumber":96,"sourceCode":"    except yaml.YAMLError:\n        return {}, text\n    if not isinstance(metadata, dict):\n        return {}, text\n    return metadata, parts[2].lstrip()\n\ndef memory_slug(name: str) -> str:\n    slug = re.sub(r\"[^\\w]+\", \"-\", name.lower()).strip(\"-_\")\n    return slug or \"memory\"\n\ndef memory_path(filename: str, allow_index: bool = False) -> Path:\n    if Path(filename).name != filename:\n        raise ValueError(f\"Invalid memory filename: {filename}\")\n    if filename == MEMORY_INDEX.name and not allow_index:\n        raise ValueError(\"The memory index is not a memory record\")\n\n    root = MEMORY_DIR.resolve()\n    if not root.is_relative_to(WORKDIR.resolve()):\n        raise ValueError(\"Memory directory escapes the workspace\")\n    path = (root / filename).resolve()\n    if not path.is_relative_to(root):\n        raise ValueError(f\"Memory path escapes the store: {filename}\")\n    return path\n\ndef _memory_slug(name: str) -> str:\n    return memory_slug(name)\n\ndef _normalized_memory_text(value: str) -> str:\n    return \" \".join(value.lower().split())\n\ndef should_store_memory(candidate: dict, existing: list[dict]) -> bool:\n    \"\"\"Accept durable records that are not temporary or already stored.\"\"\"\n    if not isinstance(candidate, dict):\n        return False\n    if candidate.get(\"scope\") != \"persistent\":\n        return False\n    if candidate.get(\"type\") not in MEMORY_TYPES:","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s09_memory/code.py#L78-L114","documentation":"Raised by memory_path() in s09_memory/code.py as a startup/configuration sanity check: MEMORY_DIR.resolve() must lie inside WORKDIR.resolve(), or every memory operation aborts. It is an environment invariant, not user-input dependent — if it fires, the memory directory was mounted/placed outside the agent sandbox, and the module refuses rather than write outside the workspace. This fires before any filename logic, so all memory tools fail uniformly.","triggerScenarios":"MEMORY_DIR configured (or defaulted) to something like ~/.memory or /var/data/memory while WORKDIR is the project directory; MEMORY_DIR or WORKDIR being a symlink whose target resolves outside the expected tree.","commonSituations":"Env var pointing the memory store at a shared location outside the workspace; project moved so a previously-inside relative path now resolves elsewhere; symlinked home/project directories changing resolve() outcomes across machines.","solutions":["Place the memory store inside the workspace, e.g. WORKDIR / '.agent' / 'memory', and update the configuration","If an outside location is truly required, extend WORKDIR to a common ancestor containing both — the module will not allow bypassing this","Check for symlinks on MEMORY_DIR/WORKDIR and use real paths in configuration so resolve() is stable"],"exampleFix":"# before (env)\nMEMORY_DIR=/var/lib/agent/memory   # outside WORKDIR\n# after\nMEMORY_DIR=<WORKDIR>/.agent/memory  # inside WORKDIR","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\nwd = WORKDIR.resolve()\nmd = MEMORY_DIR.resolve()\nassert md.is_relative_to(wd), (\n    f\"MEMORY_DIR {md} must live inside WORKDIR {wd}; \"\n    f\"set it to {wd / '.agent' / 'memory'}\"\n)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the memory store inside the workspace directory","Use resolved real paths for WORKDIR/MEMORY_DIR configuration to avoid symlink surprises","Run this containment assertion at startup, before any memory tool is registered"],"tags":["memory","configuration","security","sandbox","environment"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}