diff --git a/packages/nx/src/project-graph/project-graph.ts b/packages/nx/src/project-graph/project-graph.ts index d60f91da849..5e850cfdeb7 100644 --- a/packages/nx/src/project-graph/project-graph.ts +++ b/packages/nx/src/project-graph/project-graph.ts @@ -416,10 +416,36 @@ export async function createProjectGraphAndSourceMapsAsync( } export function preventRecursionInGraphConstruction() { - // preventRecursionInGraphConstruction -> callee -> ... - // slice removes preventRecursionInGraphConstruction and its caller, - // which is useful when using this function to detect recursion in buildProjectGraphAndSourceMapsWithoutDaemon - const stackframes = getCallSites().slice(2); + const allFrames = getCallSites(); + + // Find the first occurrence of buildProjectGraphAndSourceMapsWithoutDaemon in the call stack. + // This represents the current invocation and should be skipped for the recursion check. + const firstOccurrenceIndex = allFrames.findIndex( + (f) => + f.getFunctionName() === buildProjectGraphAndSourceMapsWithoutDaemon.name + ); + + let stackframes: NodeJS.CallSite[]; + + if (firstOccurrenceIndex !== -1) { + // Skip the current invocation frame and any consecutive frames with the same function name. + // Some runtimes (e.g. Bun) include extra async frames for the same call, which would + // otherwise cause a false positive loop detection. + let startIndex = firstOccurrenceIndex + 1; + while ( + startIndex < allFrames.length && + allFrames[startIndex].getFunctionName() === + buildProjectGraphAndSourceMapsWithoutDaemon.name + ) { + startIndex++; + } + stackframes = allFrames.slice(startIndex); + } else { + // If buildProjectGraphAndSourceMapsWithoutDaemon is not in the stack (e.g., when called + // from daemon client), fall back to the original slice(2) behavior. + // preventRecursionInGraphConstruction -> callee -> ... + stackframes = allFrames.slice(2); + } if ( stackframes.some((f) => {