{"record":{"id":"86b3e7cc48fb2583","repo":"PrefectHQ/fastmcp","slug":"file-not-found-self-file-path","errorCode":null,"errorMessage":"File not found: {self.file_path}","messagePattern":"File not found: (.+?)","errorType":"exception","errorClass":"FileNotFoundError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/providers/skills/skill_provider.py","lineNumber":163,"sourceCode":"    def get_meta(self) -> dict[str, Any]:\n        meta = super().get_meta()\n        fastmcp = cast(dict[str, Any], meta[\"fastmcp\"])\n        fastmcp[\"skill\"] = {\n            \"name\": self.skill_info.name,\n        }\n        return meta\n\n    async def read(self) -> str | bytes | ResourceResult:\n        \"\"\"Read the file content.\"\"\"\n        # Security: reject traversal, absolute-path injection, null bytes, and\n        # symlink escapes before touching the filesystem.\n        try:\n            full_path = safe_join(self.skill_info.path, self.file_path)\n        except PathEscapeError as e:\n            raise ValueError(f\"Invalid path: {e}\") from e\n\n        if not full_path.exists():\n            raise FileNotFoundError(f\"File not found: {self.file_path}\")\n\n        mime_type, _ = mimetypes.guess_type(str(full_path))\n        if mime_type and mime_type.startswith(\"text/\"):\n            return full_path.read_text(encoding=\"utf-8\")\n        else:\n            return full_path.read_bytes()\n\n\n# -----------------------------------------------------------------------------\n# SkillProvider - handles a SINGLE skill folder\n# -----------------------------------------------------------------------------\n\n\nclass SkillProvider(Provider):\n    \"\"\"Provider that exposes a single skill folder as MCP resources.\n\n    Each skill folder must contain a main file (default: SKILL.md) and may\n    contain additional supporting files.","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/providers/skills/skill_provider.py#L145-L181","documentation":"SkillFileResource.read() raises FileNotFoundError when the resolved file inside a skill folder does not exist on disk. The path is first validated by safe_join (rejecting traversal/absolute injection), then checked with full_path.exists() before reading. This means the URI resolved to a real resource entry but the underlying file was deleted, renamed, or never created.","triggerScenarios":"Reading a skill:// resource whose file_path points to a file that no longer exists — e.g. the skill folder was modified after the provider enumerated it, the client requests a supporting file that isn't in the skill directory, or a stale ResourceTemplate match references a removed file.","commonSituations":"Skill directories regenerated or cleaned by a build/deploy step while a server holds cached resource listings; typo'd file names in client URIs; skills synced from git where files were moved; container images that stripped non-main files from the skill folder.","solutions":["List the current files in the skill folder (skill://{name}/_manifest resource) and use an existing file_path in the URI","Re-add or restore the missing file in the skill directory on disk","Refresh the server's resource listing (restart or re-enumerate) if the cache is stale after files changed","Verify the skill_path configured on SkillProvider points at the directory you think it does"],"exampleFix":"// before: guessing a supporting file name\nawait client.read_resource('skill://my-skill/examples/demo.py')\n// after: discover real file names from the manifest first\nmanifest = await client.read_resource('skill://my-skill/_manifest')\n# pick a file_path actually present in the manifest, then read it\nawait client.read_resource('skill://my-skill/SKILL.md')","handlingStrategy":"try-catch","validationCode":"import os\np = os.path.join(skill_dir, 'examples/demo.py')\nassert not os.path.isabs('examples/demo.py') and os.path.exists(p), 'file missing from skill folder'","typeGuard":"from pathlib import Path\ndef file_exists_in_skill(skill_dir: Path, rel: str) -> bool:\n    candidate = (skill_dir / rel).resolve()\n    return candidate.is_file() and candidate.is_relative_to(skill_dir.resolve())","tryCatchPattern":"try:\n    content = await client.read_resource('skill://my-skill/examples/demo.py')\nexcept FileNotFoundError:\n    manifest = await client.read_resource('skill://my-skill/_manifest')\n    content = await pick_existing_file(manifest)","preventionTips":["Read the _manifest resource to discover valid file names instead of guessing","Keep skill folders immutable during a server session; redeploy atomically","Check os.path.exists on the skill folder after deploys before serving","Pin/sync skill directories from a single source of truth"],"tags":["filesystem","file-not-found","resources","skills"],"backgroundTag":"file-not-found","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}