{"record":{"id":"d97f76a5c5d50d3a","repo":"jackwener/OpenCLI","slug":"failed-to-prune-trace-artifact-dir-err-insta","errorCode":null,"errorMessage":"Failed to prune trace artifact ${dir}: ${err instanceof Error ? err.message : String(err)}","messagePattern":"Failed to prune trace artifact (.+?): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/observation/retention.ts","lineNumber":127,"sourceCode":"    remaining = remaining.filter((entry) => entry.dir !== victim.dir);\n  }\n\n  let remainingBytes = remaining.reduce((sum, entry) => sum + entry.sizeBytes, 0);\n  while (remainingBytes > policy.maxBytesPerProfile) {\n    const victim = remaining.find((entry) => !entry.protected);\n    if (!victim) break;\n    deleted.add(victim.dir);\n    remaining = remaining.filter((entry) => entry.dir !== victim.dir);\n    remainingBytes -= victim.sizeBytes;\n  }\n\n  const deletedDirs: string[] = [];\n  for (const dir of sorted.map((entry) => entry.dir).filter((dir) => deleted.has(dir))) {\n    try {\n      fs.rmSync(dir, { recursive: true, force: true });\n      deletedDirs.push(dir);\n    } catch (err) {\n      warn(`Failed to prune trace artifact ${dir}: ${err instanceof Error ? err.message : String(err)}`);\n    }\n  }\n\n  const keptEntries = entries.filter((entry) => !deletedDirs.includes(entry.dir));\n  return {\n    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[] {","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/observation/retention.ts#L109-L145","documentation":"pruneTraceArtifacts deletes old trace artifact directories with fs.rmSync after deciding which entries exceed retention limits. When the OS refuses the recursive removal (permissions, files locked by another process, EPERM/EBUSY/EACCES), the error is caught per-directory and surfaced as this warning while other directories continue to be pruned. It is non-fatal: the affected dir simply stays on disk and is reported as kept.","triggerScenarios":"fs.rmSync(dir, {recursive:true, force:true}) throws for a specific trace directory — e.g. the directory contains read-only files, is held open by another process (a running trace viewer, editor, antivirus on Windows), or the current user lacks write permission on a parent.","commonSituations":"Running retention cleanup while a debugger or trace tool has files open; traces directory owned by a different user (CI artifacts written as root, pruned as non-root); Windows antivirus or OneDrive sync locking files; NFS/EBS stale handles.","solutions":["Check permissions on the failing directory (ls -la) and chown/chmod so the process user can delete it.","Close processes holding files open in the traces directory (or retry later — the next prune pass will retry).","On Windows, exclude the traces directory from antivirus/sync tools that lock files.","If a specific dir persistently fails, delete it manually and re-run the retention pass."],"exampleFix":"// before\nfs.rmSync(dir, { recursive: true, force: true });\n// after\ntry {\n  fs.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });\n} catch (err) {\n  fs.chmodSync(dir, 0o700);\n  fs.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });\n}","handlingStrategy":"try-catch","validationCode":"const st = fs.statSync(dir);\nif (st.isDirectory() && fs.accessSync(dir, fs.constants.W_OK)) throw new Error(`no write access: ${dir}`);","typeGuard":"function isRemovable(dir: string): boolean {\n  try { fs.accessSync(dir, fs.constants.W_OK); return fs.statSync(dir).isDirectory(); } catch { return false; }\n}","tryCatchPattern":"try {\n  fs.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });\n} catch (err) {\n  const code = (err as NodeJS.ErrnoException).code;\n  console.warn(`skip ${dir}: ${code ?? err}`); // EPERM/EBUSY -> retry later\n}","preventionTips":["Close trace viewers/editors holding files open before running retention.","Run retention as the same user that created the traces.","Exclude the traces directory from antivirus/sync software.","Use rmSync maxRetries/retryDelay for transient locks."],"tags":["filesystem","permissions","retention"],"backgroundTag":"file-deletion-permission-denied","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}