{"record":{"id":"9b9e8c75cdfaa2e1","repo":"Crosstalk-Solutions/project-nomad","slug":"failed-to-read-file-stream-filename","errorCode":null,"errorMessage":"Failed to read file stream: ${filename}","messagePattern":"Failed to read file stream: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"admin/app/services/docs_service.ts","lineNumber":87,"sourceCode":"      }\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\n  private prettify(filename: string) {\n    const slug = filename.replace(/\\.md$/, '')\n    if (DocsService.TITLE_OVERRIDES[slug]) {\n      return DocsService.TITLE_OVERRIDES[slug]\n    }","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/services/docs_service.ts#L69-L105","documentation":"Thrown when the docs file exists (stats found) but getFile(fullPath, 'stream') returns a falsy stream — the storage layer could not open the file for reading despite it being stat-able. This indicates an I/O or permission-level failure rather than a missing file.","triggerScenarios":"File exists but is not readable by the process (permissions/ownership), the underlying storage backend (e.g. abstracted getFile) fails to open the path, or a race where the file is deleted between the stats check and the open call.","commonSituations":"Docs directory owned by root while the app runs as non-root, read-only filesystem mounts, NFS/object-storage-backed getFile implementations erroring, files with restrictive modes created by another user.","solutions":["Check process read permissions: run as the app user, `head <docsPath>/<slug>.md`","Fix ownership/mode: chown/chmod the docs directory to the app's uid/gid","If getFile wraps a storage SDK, inspect its logs for the underlying open failure","Handle deletion races by re-checking existence and returning a not-found response"],"exampleFix":"// before\n// docs dir owned by root; app user cannot open files\nconst doc = await docs.parseFile('faq')\n\n// after (ops fix)\n// chown -R appuser:appuser /path/to/docs && chmod -R u+rwX /path/to/docs\nconst doc = await docs.parseFile('faq')","handlingStrategy":"try-catch","validationCode":"import { access } from 'node:fs/promises'\nawait access(path.join(docsPath, `${slug}.md`), fsConstants.R_OK)\n// readable — stream failure is then unlikely","typeGuard":null,"tryCatchPattern":"try {\n  const doc = await docsService.parseFile(slug)\n} catch (e) {\n  if ((e as Error).message.includes('Failed to read file stream')) {\n    logger.error('Permissions/I-O issue on docs dir', { slug })\n    throw new ServiceUnavailableException('Docs storage unavailable')\n  }\n  throw e\n}","preventionTips":["Run the app as the user that owns the docs directory","Set explicit u+rwX on docs in container entrypoints or Dockerfile","Monitor disk space and read-only mount conditions on the docs volume"],"tags":["filesystem","permissions","io","docs"],"backgroundTag":"file-read-permission-denied","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}