{"record":{"id":"e2a2bbb1cd467c37","repo":"santifer/career-ops","slug":"cannot-read-rel-err-message","errorCode":null,"errorMessage":"cannot read ${rel || '.'}: ${err.message}","messagePattern":"cannot read (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/_lock.mjs","lineNumber":42,"sourceCode":"function sha256(buf) {\n  return 'sha256-' + createHash('sha256').update(buf).digest('hex');\n}\n\n/**\n * Hash EVERY regular file in a plugin directory tree, recursively. NOT a curated\n * subset — the entry can `import('./anything.mjs')`, so a partial hash would let\n * a rug-pull mutate an un-hashed file. Rejects symlinks (a symlinked file would\n * pass the hash while pointing elsewhere). Excludes node_modules + .git.\n *\n * @param {string} dir absolute plugin directory\n * @returns {{ files: Record<string,string>, integrity: string }}\n */\nexport function hashPluginTree(dir) {\n  const files = {};\n  const walk = (abs, rel) => {\n    let entries;\n    try { entries = readdirSync(abs, { withFileTypes: true }); }\n    catch (err) { throw new Error(`cannot read ${rel || '.'}: ${err.message}`); }\n    for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n      if (e.name === 'node_modules' || e.name === '.git') continue;\n      const childAbs = path.join(abs, e.name);\n      const childRel = rel ? `${rel}/${e.name}` : e.name;\n      // lstat (not stat) so a symlink is detected, never followed.\n      const st = lstatSync(childAbs);\n      if (st.isSymbolicLink()) throw new Error(`refusing to hash symlink: ${childRel}`);\n      if (st.isDirectory()) walk(childAbs, childRel);\n      else if (st.isFile()) files[childRel] = sha256(readFileSync(childAbs));\n      else throw new Error(`refusing to hash non-regular file: ${childRel}`);\n    }\n  };\n  walk(dir, '');\n  // Aggregate integrity = sha256 over the deterministic sorted \"rel:hash\" join.\n  const aggregate = Object.keys(files).sort().map(k => `${k}:${files[k]}`).join('\\n');\n  return { files, integrity: sha256(Buffer.from(aggregate)) };\n}\n","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/plugins/_lock.mjs#L24-L60","documentation":"hashPluginTree walks a plugin directory recursively (via its internal `walk` closure) and hashes every file to compute an integrity manifest. Before hashing, it calls readdirSync on each directory; if that fails (the directory cannot be opened/read), it throws this error wrapping the underlying message, with `rel || '.'` identifying the relative path that could not be read. It exists to convert a raw filesystem error into an error that names the plugin path being processed.","triggerScenarios":"Calling hashPluginTree(dir) where `dir` itself does not exist, is not readable by the current user, or where any subdirectory within the tree (excluding node_modules/.git) cannot be read — e.g. a directory that is actually a broken symlink resolved earlier, a permissions-restricted directory, or a race where the directory is deleted during traversal.","commonSituations":"Plugin installed with root-only permissions but the tool runs as a normal user; a plugin directory removed or renamed after the path was captured (partial uninstall, IDE cleanup); passing a typo'd or non-existent plugins directory; running on a mount that went offline; on Windows, a directory locked by another process.","solutions":["Verify the plugin directory exists and is readable: run `ls -la` on the reported path and fix permissions (chmod/chown) or correct the path passed to hashPluginTree.","Reinstall or re-extract the plugin so the directory tree is complete and readable.","Check the inner error message (the part after 'cannot read <path>: ') for the actual errno (ENOENT vs EACCES vs ELOOP) and fix accordingly.","If a subdirectory within the plugin is intentionally unreadable, exclude it from the tree or fix its permissions."],"exampleFix":"// before: hashing a path that may not exist\nconst { integrity } = hashPluginTree(userSuppliedDir);\n\n// after: check readability first\nconst dir = userSuppliedDir;\nif (!existsSync(dir) || !statSync(dir).isDirectory()) {\n  throw new Error(`plugin dir not found: ${dir}`);\n}\nconst { integrity } = hashPluginTree(dir);","handlingStrategy":"try-catch","validationCode":"import { existsSync, statSync, accessSync, constants } from 'fs';\nfunction isReadableDir(p) {\n  try {\n    if (!existsSync(p) || !statSync(p).isDirectory()) return false;\n    accessSync(p, constants.R_OK);\n    return true;\n  } catch { return false; }\n}","typeGuard":"function isPluginDir(p) {\n  return typeof p === 'string' && p.length > 0 && isReadableDir(p);\n}","tryCatchPattern":"try {\n  const { files, integrity } = hashPluginTree(dir);\n} catch (err) {\n  if (err.message.startsWith('cannot read ')) {\n    const relPath = err.message.split('cannot read ')[1].split(':')[0].trim();\n    console.error(`Plugin tree unreadable at '${relPath}' — check existence/permissions`);\n  }\n  throw err;\n}","preventionTips":["Validate the plugin directory exists and is readable before hashing.","Run the tool with a user that owns or can read the plugins directory.","Avoid deleting/renaming plugin directories while hashing is in progress.","Log the inner errno (ENOENT/EACCES) to distinguish missing vs permission issues."],"tags":["filesystem","permissions","plugin-integrity","hashing"],"backgroundTag":"file-read-failed","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}