{"record":{"id":"4cb55b0533afc006","repo":"affaan-m/ECC","slug":"refusing-to-remove-project-dir-escapes-project","errorCode":null,"errorMessage":"refusing to remove {project_dir}: escapes {projects_root}","messagePattern":"refusing to remove (.+?): escapes (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/continuous-learning-v2/scripts/instinct-cli.py","lineNumber":628,"sourceCode":"            observations_count = 0\n\n    return {\n        \"personal\": personal_count,\n        \"inherited\": inherited_count,\n        \"observations\": observations_count,\n        \"total\": personal_count + inherited_count + observations_count,\n    }\n\n\ndef _remove_project_storage(project_id: str) -> None:\n    # Defense-in-depth: resolve and confirm the target is contained within\n    # PROJECTS_DIR before recursively deleting, even though callers validate the\n    # project id. A relaxed validator or a future caller must never be able to\n    # turn this into an arbitrary-directory delete.\n    projects_root = PROJECTS_DIR.resolve()\n    project_dir = (PROJECTS_DIR / project_id).resolve()\n    if project_dir == projects_root or projects_root not in project_dir.parents:\n        raise ValueError(f\"refusing to remove {project_dir}: escapes {projects_root}\")\n    if project_dir.exists():\n        shutil.rmtree(project_dir)\n\n\ndef _project_instinct_ids(project_dir: Path, source_type: str) -> set[str]:\n    instinct_dir = project_dir / \"instincts\" / source_type\n    return {\n        inst.get(\"id\")\n        for inst in _load_instincts_from_dir(instinct_dir, source_type, \"project\")\n        if inst.get(\"id\")\n    }\n\n\ndef _merge_instinct_dir(from_dir: Path, into_dir: Path, existing_ids: set[str]) -> tuple[int, int]:\n    moved = 0\n    skipped = 0\n    if not from_dir.exists():\n        return moved, skipped","sourceCodeStart":610,"sourceCodeEnd":646,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/skills/continuous-learning-v2/scripts/instinct-cli.py#L610-L646","documentation":"Error \"refusing to remove {project_dir}: escapes {projects_root}\" thrown in affaan-m/ECC.","triggerScenarios":"Thrown at skills/continuous-learning-v2/scripts/instinct-cli.py:628 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["The resolved project directory escapes the projects root, so deletion is refused. Fix the project_id so it names a child of PROJECTS_DIR (no '..', no symlink tricks).","If the projects root itself was moved or symlinked, restore PROJECTS_DIR to a real directory and retry.","Remove the directory manually only after confirming it is not shared with other data."],"exampleFix":"# ensure project_id matches ^[A-Za-z0-9._-]+$ and PROJECTS_DIR is a real dir:\nrm -rf -- \"$PROJECTS_DIR/$safe_project_id\"","handlingStrategy":null,"validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}