{"record":{"id":"83a51196f28fcf78","repo":"affaan-m/ECC","slug":"output-parent-directory-must-already-exist","errorCode":null,"errorMessage":"output parent directory must already exist","messagePattern":"output parent directory must already exist","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/workflow.py","lineNumber":183,"sourceCode":"    return float(duration)\n\n\nclass _SafeOutput:\n    \"\"\"Descriptor-bound output tree with no-follow traversal and atomic writes.\"\"\"\n\n    def __init__(self, root: Path) -> None:\n        self._root_fd = -1\n        if not hasattr(os, \"O_NOFOLLOW\") or not hasattr(os, \"O_DIRECTORY\"):\n            raise RuntimeError(\"secure output requires O_NOFOLLOW and O_DIRECTORY\")\n        if root.exists() or root.is_symlink():\n            metadata = root.lstat()\n            if stat.S_ISLNK(metadata.st_mode):\n                raise ValueError(\"output root must not be a symlink\")\n            if not stat.S_ISDIR(metadata.st_mode):\n                raise ValueError(\"output root must be a directory\")\n        else:\n            if not root.parent.is_dir():\n                raise ValueError(\"output parent directory must already exist\")\n            root.mkdir(mode=0o700)\n        self.root = root\n        self._root_fd = os.open(root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)\n        self._written: list[str] = []\n\n    def close(self) -> None:\n        if self._root_fd >= 0:\n            os.close(self._root_fd)\n            self._root_fd = -1\n\n    def __del__(self) -> None:\n        self.close()\n\n    def _open_dir(self, parts: tuple[str, ...], *, create: bool) -> int:\n        current = os.dup(self._root_fd)\n        try:\n            for part in parts:\n                if not part or part in {\".\", \"..\"} or \"/\" in part:","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/workflow.py#L165-L201","documentation":"When the output root does not exist, _SafeOutput will create it (mode 0o700) but deliberately refuses to create parent directories. If the immediate parent of the requested root is not an existing directory, __init__ raises this error so that path assumptions and permissions stay explicit and no accidental deep trees are created.","triggerScenarios":"_SafeOutput(Path('a/b/c')) where 'a/b' or 'a/b/c''s parent does not exist; passing outputs/<run-id>/... when outputs/ was deleted; a typo in the parent directory name.","commonSituations":"CI workspaces where prior artifact dirs were cleaned before the run; user-supplied output paths with non-existent nested parents; typos like 'ouputs/run1'.","solutions":["Create the parent directory first: Path('a/b').mkdir(parents=True, exist_ok=True) before constructing _SafeOutput","Verify/fix the output path spelling in your config or CLI arguments","Have the calling script (e.g. run_workflow's CLI entry) prepare the base output directory before invoking _SafeOutput","Run from the intended working directory so relative paths resolve to existing parents"],"exampleFix":"# before\nout = _SafeOutput(Path('artifacts/run1'))  # artifacts/ does not exist\n\n# after\nPath('artifacts').mkdir(parents=True, exist_ok=True)\nout = _SafeOutput(Path('artifacts/run1'))","handlingStrategy":"validation","validationCode":"def ensure_parent_exists(root):\n    if not root.parent.is_dir():\n        root.parent.mkdir(parents=True, exist_ok=True)","typeGuard":"def parent_ready(p) -> bool:\n    return p.parent.is_dir()","tryCatchPattern":"try:\n    out = _SafeOutput(root)\nexcept ValueError as e:\n    if 'parent directory must already exist' in str(e):\n        root.parent.mkdir(parents=True, exist_ok=True)\n        out = _SafeOutput(root)\n    else:\n        raise","preventionTips":["Create the base output directory in your pipeline bootstrap step","Double-check spelling of configured output paths","Use absolute paths to avoid cwd surprises in CI","Run a path dry-run (parent check) before expensive work"],"tags":["filesystem","directory","validation"],"backgroundTag":"directory-not-found","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}