{"record":{"id":"76bbd591444cf8d8","repo":"jackwener/OpenCLI","slug":"failed-to-list-trace-artifacts-in-tracesdir","errorCode":null,"errorMessage":"Failed to list trace artifacts in ${tracesDir}: ${err instanceof Error ? err.message : String(err)}","messagePattern":"Failed to list trace artifacts in (.+?): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/observation/retention.ts","lineNumber":151,"sourceCode":"    scanned: entries.length,\n    deleted: deletedDirs,\n    kept: keptEntries.map((entry) => entry.dir),\n    totalBytesBefore,\n    totalBytesAfter: keptEntries.reduce((sum, entry) => sum + entry.sizeBytes, 0),\n  };\n}\n\nfunction readTraceEntries(\n  tracesDir: string,\n  protectedDirs: Set<string>,\n  warn: (message: string) => void,\n): TraceEntry[] {\n  let names: string[];\n  try {\n    names = fs.readdirSync(tracesDir);\n  } catch (err) {\n    if (isEnoent(err)) return [];\n    warn(`Failed to list trace artifacts in ${tracesDir}: ${err instanceof Error ? err.message : String(err)}`);\n    return [];\n  }\n\n  const entries: TraceEntry[] = [];\n  for (const name of names) {\n    const dir = path.join(tracesDir, name);\n    try {\n      const stat = fs.statSync(dir);\n      if (!stat.isDirectory()) continue;\n      entries.push({\n        dir,\n        createdAtMs: readCreatedAtMs(dir, stat.mtimeMs),\n        sizeBytes: directorySize(dir),\n        protected: protectedDirs.has(path.resolve(dir)),\n      });\n    } catch (err) {\n      if (!isEnoent(err)) {\n        warn(`Failed to inspect trace artifact ${dir}: ${err instanceof Error ? err.message : String(err)}`);","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/observation/retention.ts#L133-L169","documentation":"readTraceEntries lists the traces directory with fs.readdirSync to build retention entries. Any listing error other than ENOENT (missing dir is treated as \"no traces\") triggers this warning and returns an empty list. The library throws it so callers still get a safe empty result instead of crashing retention.","triggerScenarios":"fs.readdirSync(tracesDir) throws for reasons other than the directory not existing: EACCES (no read permission), ENOTDIR (tracesDir path is a file), EIO (disk issue), or EMFILE/ENFILE (too many open files).","commonSituations":"tracesDir misconfigured to point at a regular file; permissions changed after a container/user switch; ulimit too low producing EMFILE under heavy load; networked filesystem briefly unavailable.","solutions":["Verify tracesDir exists and is a directory (fs.statSync(tracesDir).isDirectory()); fix the configured path if not.","Grant the process user read+execute permission on the traces directory.","If the error is EMFILE/ENFILE, raise the file-descriptor limit (ulimit -n) or reduce concurrent I/O.","Create the directory with mkdirSync(tracesDir, {recursive:true}) if you want an empty result to be legitimate."],"exampleFix":"// before\nwarn(`Failed to list trace artifacts in ${tracesDir}: ...`);\n// after\nfs.mkdirSync(tracesDir, { recursive: true }); // ensure dir exists before listing\nconst stats = fs.statSync(tracesDir);\nif (!stats.isDirectory()) throw new Error(`tracesDir is not a directory: ${tracesDir}`);","handlingStrategy":"fallback","validationCode":"let ok = true;\ntry { ok = fs.statSync(tracesDir).isDirectory(); } catch { ok = false; }\nif (!ok) fs.mkdirSync(tracesDir, { recursive: true });","typeGuard":"function isReadableDir(p: string): boolean {\n  try {\n    const st = fs.statSync(p);\n    fs.accessSync(p, fs.constants.R_OK);\n    return st.isDirectory();\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  names = fs.readdirSync(tracesDir);\n} catch (err) {\n  if ((err as NodeJS.ErrnoException).code === 'ENOENT') { names = []; }\n  else { console.warn(`readdir failed: ${(err as Error).message}`); names = []; }\n}","preventionTips":["Create the traces directory at startup with mkdirSync recursive.","Point tracesDir at a directory, never a file path.","Keep file-descriptor limits healthy (ulimit -n) under load.","Verify permissions after container/user switches."],"tags":["filesystem","permissions","directory-listing"],"backgroundTag":"readdir-access-denied","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}