{"record":{"id":"58e23302362dd914","repo":"Crosstalk-Solutions/project-nomad","slug":"file-not-found-filename","errorCode":null,"errorMessage":"File not found: ${filename}","messagePattern":"File not found: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"admin/app/services/docs_service.ts","lineNumber":82,"sourceCode":"\n  async parseFile(_filename: string) {\n    try {\n      if (!_filename) {\n        throw new Error('Filename is required')\n      }\n\n      const filename = _filename.endsWith('.md') ? _filename : `${_filename}.md`\n\n      // Prevent path traversal — resolved path must stay within the docs directory\n      const basePath = path.resolve(this.docsPath)\n      const fullPath = path.resolve(path.join(this.docsPath, filename))\n      if (!fullPath.startsWith(basePath + path.sep)) {\n        throw new Error('Invalid document slug')\n      }\n\n      const fileExists = await getFileStatsIfExists(fullPath)\n      if (!fileExists) {\n        throw new Error(`File not found: ${filename}`)\n      }\n\n      const fileStream = await getFile(fullPath, 'stream')\n      if (!fileStream) {\n        throw new Error(`Failed to read file stream: ${filename}`)\n      }\n      const content = await streamToString(fileStream)\n      return this.parse(content)\n    } catch (error) {\n      throw new InternalServerErrorException(`Error parsing file: ${(error as Error).message}`)\n    }\n  }\n\n  private static readonly TITLE_OVERRIDES: Record<string, string> = {\n    'faq': 'FAQ',\n    'community-add-ons': 'Community Add-Ons',\n  }\n","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/services/docs_service.ts#L64-L100","documentation":"Thrown by DocsService.parseFile when the resolved file path passes the traversal guard but getFileStatsIfExists reports no fs.Stats — i.e. the .md file does not exist at that location (a non-existent slug, or the file was deleted/renamed). The '.md' extension is appended automatically if missing.","triggerScenarios":"parseFile('nonexistent-doc') where docs/nonexistent-doc.md is absent; requesting a doc whose file was renamed or moved; case-sensitivity mismatch on case-sensitive filesystems; docs directory not mounted in the container.","commonSituations":"Stale frontend links after docs restructuring, docs volume not mounted in production, filename case mismatch (FAQ.md vs faq.md) between macOS dev and Linux prod, files not committed to the repo/deployment artifact.","solutions":["Verify the file exists on disk: ls <docsPath>/<slug>.md with exact casing","Check the docsPath configuration/mount in the running environment (container volume, env var)","Fix or remove the stale link/alias pointing to the renamed doc","Normalize doc filenames to lowercase and enforce it when creating docs"],"exampleFix":"// before\nconst doc = await docs.parseFile('Setup-Guide') // file is setup-guide.md\n\n// after\nconst doc = await docs.parseFile('setup-guide')","handlingStrategy":"validation","validationCode":"import { getFileStatsIfExists } from '<storage util>'\nconst p = path.join(docsPath, `${slug}.md`)\nif (!(await getFileStatsIfExists(p))) throw new NotFoundException(`Doc ${slug} not found`)","typeGuard":"const docExists = async (slug: string): Promise<boolean> =>\n  Boolean(await getFileStatsIfExists(path.join(docsPath, `${slug}.md`)))","tryCatchPattern":"try {\n  const doc = await docsService.parseFile(slug)\n} catch (e) {\n  if ((e as Error).message.startsWith('File not found'))\n    throw new NotFoundException(`Doc '${slug}' not found`)\n  throw e\n}","preventionTips":["Keep an index/manifest of doc slugs and validate links against it","Normalize filenames to lowercase kebab-case at authoring time","Test doc routes in CI on a case-sensitive filesystem"],"tags":["file-not-found","docs","filesystem"],"backgroundTag":"file-not-found","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}