{"record":{"id":"970ec08ec4c99929","repo":"PrefectHQ/fastmcp","slug":"not-a-directory-self-path","errorCode":null,"errorMessage":"Not a directory: {self.path}","messagePattern":"Not a directory: (.+?)","errorType":"exception","errorClass":"NotADirectoryError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/resources/types.py","lineNumber":165,"sourceCode":"\n    @property\n    def _async_path(self) -> AsyncPath:\n        return AsyncPath(self.path)\n\n    @pydantic.field_validator(\"path\")\n    @classmethod\n    def validate_absolute_path(cls, path: Path) -> Path:\n        \"\"\"Ensure path is absolute.\"\"\"\n        if not path.is_absolute():\n            raise ValueError(\"Path must be absolute\")\n        return path\n\n    async def list_files(self) -> list[Path]:\n        \"\"\"List files in the directory.\"\"\"\n        if not await self._async_path.exists():\n            raise FileNotFoundError(f\"Directory not found: {self.path}\")\n        if not await self._async_path.is_dir():\n            raise NotADirectoryError(f\"Not a directory: {self.path}\")\n\n        pattern = self.pattern or \"*\"\n\n        glob_fn = self._async_path.rglob if self.recursive else self._async_path.glob\n        try:\n            return [Path(p) async for p in glob_fn(pattern) if await p.is_file()]\n        except Exception as e:\n            raise ResourceError(f\"Error listing directory {self.path}\") from e\n\n    @override\n    async def read(self) -> ResourceResult:\n        \"\"\"Read the directory listing.\"\"\"\n        try:\n            files: list[Path] = await self.list_files()\n\n            file_list = [str(f.relative_to(self.path)) for f in files]\n\n            content = json.dumps({\"files\": file_list}, indent=2)","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/resources/types.py#L147-L183","documentation":"DirectoryResource.list_files() raises NotADirectoryError when the configured `path` exists but is a file (or other non-directory), so a directory listing cannot be produced. This separates 'path missing' (210) from 'path exists but wrong kind of node'.","triggerScenarios":"Construct DirectoryResource with `path` set to an existing regular file (or symlink to a file, socket, etc.); the is_dir() check at fastmcp_slim/fastmcp/resources/types.py:165 fails when read()/list_files() is called.","commonSituations":"Config points at a single file (e.g. /etc/passwd or a config.json) instead of its parent directory; path is a symlink whose target changed to a file; copy/paste error swapping a file path for the directory that contains it.","solutions":["Point DirectoryResource.path at a directory instead of a file.","If you meant to expose a single file, use FileResource (or the appropriate resource type) rather than DirectoryResource.","Add a startup assertion: path.is_dir() before registering the resource.","If a symlink is involved, confirm it resolves to a directory (Path.resolve().is_dir())."],"exampleFix":"// before\nDirectoryResource(path=Path('/data/config.json'), name='config')  # it's a file\n\n// after\nfrom fastmcp.resources import FileResource\nFileResource(path=Path('/data/config.json'), name='config')","handlingStrategy":"validation","validationCode":"from pathlib import Path\npath = Path('/data/config.json')\nif path.exists() and not path.is_dir():\n    raise NotADirectoryError(f'Expected a directory: {path}')","typeGuard":null,"tryCatchPattern":"try:\n    result = await resource.read()\nexcept NotADirectoryError as e:\n    logger.error('path is not a directory: %s', e)","preventionTips":["Check path.is_dir() before constructing DirectoryResource.","Use FileResource for single files.","Resolve symlinks and verify the target type."],"tags":["filesystem","resources","wrong-path-type"],"backgroundTag":"not-a-directory","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}