{"record":{"id":"edc11e1d57a6ac63","repo":"caolan/async","slug":"async-auto-cannot-execute-tasks-due-to-a-recursive","errorCode":null,"errorMessage":"async.auto cannot execute tasks due to a recursive dependency","messagePattern":"async\\.auto cannot execute tasks due to a recursive dependency","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/auto.js","lineNumber":303,"sourceCode":"\n    function checkForDeadlocks() {\n        // Kahn's algorithm\n        // https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm\n        // http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html\n        var currentTask;\n        var counter = 0;\n        while (readyToCheck.length) {\n            currentTask = readyToCheck.pop();\n            counter++;\n            getDependents(currentTask).forEach(dependent => {\n                if (--uncheckedDependencies[dependent] === 0) {\n                    readyToCheck.push(dependent);\n                }\n            });\n        }\n\n        if (counter !== numTasks) {\n            throw new Error(\n                'async.auto cannot execute tasks due to a recursive dependency'\n            );\n        }\n    }\n\n    function getDependents(taskName) {\n        var result = [];\n        Object.keys(tasks).forEach(key => {\n            const task = tasks[key]\n            if (Array.isArray(task) && task.indexOf(taskName) >= 0) {\n                result.push(key);\n            }\n        });\n        return result;\n    }\n\n    return callback[PROMISE_SYMBOL]\n}","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/caolan/async/blob/13dfaf13f3fc809ba1c9c39d5e267ac6959cbf4a/lib/auto.js#L285-L321","documentation":"Before executing, async.auto performs a topological-sort pass (checkForDeadlocks) counting how many tasks become 'ready'. If the count never reaches the total number of tasks, the dependency graph contains a cycle and auto throws instead of hanging. No task in a cyclic graph can ever start.","triggerScenarios":"Defining tasks whose dependencies form a loop, e.g. async.auto({a: ['b', fnA], b: ['a', fnB]}). Also with longer cycles a -> b -> c -> a, or a task listing itself as a dependency: {a: ['a', fn]}.","commonSituations":"Refactoring a task graph where two tasks accidentally started depending on each other, merging generated dependency lists that reference each other, dynamic graphs built from user config containing circular references.","solutions":["Break the cycle by removing or restructuring one dependency edge","Draw the task graph and identify the loop before running","Split a mutually dependent task into two ordered tasks","If the data truly flows both ways, merge the tasks into one"],"exampleFix":"// before\nasync.auto({\n  a: ['b', stepA],\n  b: ['a', stepB]\n});\n// after\nasync.auto({\n  a: [stepA],\n  b: ['a', stepB]\n});","handlingStrategy":"validation","validationCode":"function hasCycle(tasks) {\n  const state = {};\n  const deps = k => (Array.isArray(tasks[k]) ? tasks[k][0] : []);\n  let cyclic = false;\n  function visit(k, stack) {\n    if (stack.has(k)) { cyclic = true; return; }\n    if (state[k]) return;\n    state[k] = 1;\n    deps(k).forEach(d => visit(d, new Set([...stack, k])));\n    state[k] = 2;\n  }\n  Object.keys(tasks).forEach(k => visit(k, new Set()));\n  return cyclic;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await async.auto(tasks);\n} catch (err) {\n  if (String(err.message).includes('recursive dependency')) {\n    console.error('Circular dependency in task graph');\n  } else throw err;\n}","preventionTips":["Design task graphs as a DAG and document the ordering","Add a cycle-detection test for dynamically generated graphs","Avoid mutual dependencies; prefer splitting or merging tasks"],"tags":["async","dependency-graph","deadlock"],"backgroundTag":"circular-dependency","analyzedSha":"13dfaf13f3fc809ba1c9c39d5e267ac6959cbf4a","analyzedAt":"2026-08-28T22:50:46.507Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}