Skip to content

[codex] Fix agentflow resume through converging condition paths#6489

Draft
cat0825 wants to merge 1 commit into
FlowiseAI:mainfrom
cat0825:codex/flowise-5501-converging-condition-paths
Draft

[codex] Fix agentflow resume through converging condition paths#6489
cat0825 wants to merge 1 commit into
FlowiseAI:mainfrom
cat0825:codex/flowise-5501-converging-condition-paths

Conversation

@cat0825

@cat0825 cat0825 commented Jun 7, 2026

Copy link
Copy Markdown

Summary

  • Fix Agentflow resume when a selected human-input branch converges with an unselected conditional branch.
  • Track skipped decision outputs at the edge level and propagate skipped inputs as null so merge nodes do not wait forever for branches that were not selected.
  • Replay skipped decision edges from previous finished nodes during human-input resume.
  • Add a regression test covering issue Flow execution stops at Condition node when multiple branches converge into single LLM node #5501 and extend the TypeORM Jest mock with Equal used by workspace search options.

Closes #5501

Root cause

Previously, unfulfilled condition outputs skipped target nodes by node id. In flows where an unselected branch and a selected human-input branch converge into the same downstream node, the downstream node kept waiting for the unselected branch input after resume, so execution stopped before the merge node could run.

Validation

  • PATH=/Users/qianyuhe/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH corepack pnpm@10.26.0 --dir packages/server exec jest src/utils/buildAgentflow.test.ts --runInBand
  • PATH=/Users/qianyuhe/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH corepack pnpm@10.26.0 --dir packages/server exec jest src/utils/sanitizeFlowData.test.ts --runInBand
  • PATH=/Users/qianyuhe/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH corepack pnpm@10.26.0 exec prettier --check packages/server/src/utils/buildAgentflow.ts packages/server/src/utils/buildAgentflow.test.ts packages/server/src/utils/__fixtures__/agentflowTestNode.js packages/server/__mocks__/typeorm.ts
  • PATH=/Users/qianyuhe/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH corepack pnpm@10.26.0 --dir packages/server build

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the agentflow execution logic to track and propagate skipped edges and inputs rather than just ignoring nodes, ensuring downstream merge nodes execute correctly when converging conditional paths are used (e.g., after resuming from a human-input node). It also adds corresponding tests and mock fixtures. The review feedback identifies a critical issue in the newly introduced propagateSkippedInput function, where workflows with cyclic paths (loops) could trigger infinite recursion and cause a stack overflow crash, and provides a solution using a visited set to track the recursion path.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +839 to +893
function propagateSkippedInput({
sourceId,
targetId,
graph,
nodes,
edges,
nodeExecutionQueue,
waitingNodes
}: {
sourceId: string
targetId: string
graph: Record<string, string[]>
nodes: IReactFlowNode[]
edges: IReactFlowEdge[]
nodeExecutionQueue: INodeQueue[]
waitingNodes: Map<string, IWaitingNode>
}) {
let waitingNode = waitingNodes.get(targetId)
if (!waitingNode) {
waitingNode = setupNodeDependencies(targetId, edges, nodes)
waitingNodes.set(targetId, waitingNode)
}

if (!waitingNode.receivedInputs.has(sourceId)) {
waitingNode.receivedInputs.set(sourceId, null)
}

if (!hasReceivedRequiredInputs(waitingNode)) return

waitingNodes.delete(targetId)
const combinedInputs = combineNodeInputs(waitingNode.receivedInputs)

if (combinedInputs === null) {
for (const downstreamId of graph[targetId] || []) {
propagateSkippedInput({
sourceId: targetId,
targetId: downstreamId,
graph,
nodes,
edges,
nodeExecutionQueue,
waitingNodes
})
}
return
}

if (!nodeExecutionQueue.some((queuedNode) => queuedNode.nodeId === targetId)) {
nodeExecutionQueue.push({
nodeId: targetId,
data: combinedInputs,
inputs: Object.fromEntries(waitingNode.receivedInputs)
})
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In workflows with cyclic paths (loops), recursive propagation of skipped inputs can lead to infinite recursion and a Maximum call stack size exceeded crash. To prevent this, track the active recursion path using a visited set (acting as a recursion stack) and return early if a cycle is detected.

function propagateSkippedInput({
    sourceId,
    targetId,
    graph,
    nodes,
    edges,
    nodeExecutionQueue,
    waitingNodes,
    visited = new Set<string>()
}: {
    sourceId: string
    targetId: string
    graph: Record<string, string[]>
    nodes: IReactFlowNode[]
    edges: IReactFlowEdge[]
    nodeExecutionQueue: INodeQueue[]
    waitingNodes: Map<string, IWaitingNode>
    visited?: Set<string>
}) {
    if (visited.has(targetId)) return
    visited.add(targetId)

    let waitingNode = waitingNodes.get(targetId)
    if (!waitingNode) {
        waitingNode = setupNodeDependencies(targetId, edges, nodes)
        waitingNodes.set(targetId, waitingNode)
    }

    if (!waitingNode.receivedInputs.has(sourceId)) {
        waitingNode.receivedInputs.set(sourceId, null)
    }

    if (!hasReceivedRequiredInputs(waitingNode)) {
        visited.delete(targetId)
        return
    }

    waitingNodes.delete(targetId)
    const combinedInputs = combineNodeInputs(waitingNode.receivedInputs)

    if (combinedInputs === null) {
        for (const downstreamId of graph[targetId] || []) {
            propagateSkippedInput({
                sourceId: targetId,
                targetId: downstreamId,
                graph,
                nodes,
                edges,
                nodeExecutionQueue,
                waitingNodes,
                visited
            })
        }
        visited.delete(targetId)
        return
    }

    if (!nodeExecutionQueue.some((queuedNode) => queuedNode.nodeId === targetId)) {
        nodeExecutionQueue.push({
            nodeId: targetId,
            data: combinedInputs,
            inputs: Object.fromEntries(waitingNode.receivedInputs)
        })
    }
    visited.delete(targetId)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flow execution stops at Condition node when multiple branches converge into single LLM node

1 participant