{"record":{"id":"164d19dae73eb7d6","repo":"affaan-m/ECC","slug":"too-many-task-directories","errorCode":null,"errorMessage":"Too many task directories.","messagePattern":"Too many task directories\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/coordination-inventory.js","lineNumber":241,"sourceCode":"  const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);\n  try {\n    const stat = fs.fstatSync(fd);\n    if (!stat.isFile() || stat.size > limit) throw new Error('Input exceeds file limit.');\n    const buffer = Buffer.alloc(limit + 1);\n    let size = 0; let count;\n    do { count = fs.readSync(fd, buffer, size, buffer.length - size, null); size += count; } while (count && size < buffer.length);\n    if (size > limit) throw new Error('Input exceeds file limit.');\n    return { content: buffer.subarray(0,size).toString('utf8'), modifiedAt: stat.mtime.toISOString() };\n  } finally { fs.closeSync(fd); }\n}\nfunction readJson(file) {\n  try { return JSON.parse(readBounded(file).content); }\n  catch (error) { throw new Error(error.message === 'Input exceeds file limit.' ? error.message : 'Cannot read coordination JSON.'); }\n}\nfunction collectTaskFiles(directory) {\n  try {\n    const entries = fs.readdirSync(directory, { withFileTypes: true }).filter(e => e.isDirectory() && !e.name.startsWith('.')).sort((a,b) => a.name < b.name ? -1 : 1);\n    if (entries.length > 64) throw new Error('Too many task directories.');\n    const tasks = []; const unreadable = [];\n    for (const entry of entries) {\n      let loaded = false;\n      for (const name of ['STATUS.md', 'status.md']) {\n        try {\n          const data = readBounded(path.join(directory, entry.name, name), 65536);\n          const parsed = parseWorkerStatus(data.content);\n          let heartbeatAt = null;\n          try { if (parsed.updated) heartbeatAt = timestamp(parsed.updated); } catch { /* Unknown timestamp, not a heartbeat. */ }\n          tasks.push(normalizeTask({ id: entry.name, paths: [], status: parsed.state || 'unknown', heartbeatAt, statusFileModifiedAt: data.modifiedAt }));\n          loaded = true; break;\n        } catch { /* Try legacy lowercase status filename; report unreadable below. */ }\n      }\n      if (!loaded) unreadable.push(entry.name);\n    }\n    return { status: unreadable.length ? 'partial' : 'ok', tasks, unreadable };\n  } catch { return { status: 'unavailable', tasks: [], unreadable: [] }; }\n}","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/coordination-inventory.js#L223-L259","documentation":"collectTaskFiles scans a coordination directory for per-task subdirectories and throws 'Too many task directories.' when more than 64 non-hidden subdirectories are found. This is a fan-out guard: the inventory refuses to iterate an unbounded number of task folders, treating an excessive count as suspicious or a misconfiguration rather than silently scanning everything.","triggerScenarios":"Calling collectTaskFiles(directory) (directly or via main) when the directory contains 65 or more visible subdirectories (entries that are directories and do not start with '.'). Note the error is caught internally and the function returns { status: 'unavailable' }.","commonSituations":"Many agents/CI jobs each created a task directory that was never cleaned up after completion, the script was pointed at the wrong root (e.g. a workspaces root containing dozens of projects instead of the coordination dir), or tests/fixtures accumulated hundreds of task folders over time.","solutions":["Clean up stale task directories: archive or delete folders for completed/abandoned tasks until the count is at or below 64.","Verify the directory argument is the intended coordination/tasks root, not a broader parent directory; fix the path or config if wrong.","If more than 64 concurrent tasks is legitimate for your workflow, raise the 64-entry cap in collectTaskFiles (scripts/lib/coordination-inventory.js:241) or shard tasks across multiple directories.","Because collectTaskFiles swallows the error and returns status 'unavailable', check the inventory output field to confirm this guard — not a permissions problem — is the actual cause."],"exampleFix":"// before: guard trips after unbounded task accumulation\nif (entries.length > 64) throw new Error('Too many task directories.');\n\n// after: archive completed tasks before scanning, keep cap meaningful\narchiveCompletedTasks(directory, 64);\nconst entries = fs.readdirSync(directory, { withFileTypes: true }).filter(e => e.isDirectory() && !e.name.startsWith('.'));\nif (entries.length > 64) throw new Error('Too many task directories.');","handlingStrategy":"validation","validationCode":"const count = fs.readdirSync(tasksDir, { withFileTypes: true })\n  .filter(e => e.isDirectory() && !e.name.startsWith('.')).length;\nif (count > 64) throw new Error(`${tasksDir} has ${count} task dirs; archive completed tasks first`);","typeGuard":null,"tryCatchPattern":"const result = collectTaskFiles(dir);\nif (result.status === 'unavailable') {\n  // likely the >64-directories guard (or an IO error) — inspect and prune dir, then retry\n  pruneStaleTaskDirs(dir);\n}","preventionTips":["Archive or delete task directories as part of task completion, not later.","Run a periodic cleanup job so the directory stays under the 64-dir cap.","Point the tool at the specific tasks directory, not a broad parent.","Check result.status === 'unavailable' in output — collectTaskFiles swallows this error."],"tags":["filesystem","limit-exceeded","resource-cleanup","nodejs"],"backgroundTag":"value-out-of-range","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}