crewAIInc / crewAIInc/crewAI

[BUG] or_() join cancels parallel listener branches that should complete

Open
#7,183 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
58.8k
Forks
8.5k
Avg merge
1d 15h
Merged PRs (30d)
109

Description

[BUG] or_() join cancels parallel listener branches that should complete

Description

When two listeners are triggered by the same method and their completion events
feed a downstream or_() listener, the runtime treats the two producer
listeners as a first-wins race. Once one producer completes, it cancels the
other producer.

or_(branch_a, branch_b) should control when the downstream listener fires. It
should not change the execution semantics of branch_a and branch_b
themselves. Both were independently triggered by their shared parent and may
contain required state updates, tool calls, or other side effects.

Steps to reproduce

import asyncio

from crewai.flow.flow import Flow, listen, or_, start


completed = []
joined = 0


class ParallelFanoutOrFlow(Flow):
    @start()
    def begin(self):
        return "begin"

    @listen(begin)
    async def fast_branch(self):
        await asyncio.sleep(0)
        completed.append("fast")

    @listen(begin)
    async def slow_branch(self):
        await asyncio.sleep(0.05)
        completed.append("slow")

    @listen(or_(fast_branch, slow_branch))
    def join(self):
        global joined
        joined += 1


asyncio.run(ParallelFanoutOrFlow().kickoff_async())
print(completed)
print(joined)

Actual behavior

Only the faster branch reliably completes. The slower listener is cancelled by
_execute_racing_listeners().

For synchronous listeners executed through asyncio.to_thread(), cancelling
the asyncio task cannot stop the worker thread, so the branch may instead
continue after the runtime considers it cancelled.

Expected behavior

  • Both independently triggered producer listeners complete.
  • The downstream or_() listener fires exactly once after the first producer
    completes.
  • The OR condition does not cancel event producers.

Root cause

Flow._build_racing_groups() converts the events referenced by a downstream
OR condition back into method names. During dispatch,
_get_racing_group_for_listeners() intersects those event names with the
listener methods triggered by the current parent. If both names are present,
_execute_racing_listeners() runs them as a first-wins race and cancels the
loser.

The runtime already tracks _fired_or_listeners, which prevents the downstream
OR listener from firing more than once. Producer cancellation is unnecessary.

Proposed fix

Execute every listener selected by _find_triggered_methods() using the normal
asyncio.gather() path. Keep _fired_or_listeners responsible for deduplicating
the downstream OR listener.

Add a regression test that asserts both producer branches complete while the
OR listener fires once.

Environment

  • CrewAI revision: 381fef73be1803d0b4676f88cbae57d332cb9cac
  • Python: 3.13
  • OS: Windows

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Flow._build_racing_groups(), _get_racing_group_for_listeners(), and _execute_racing_listeners(), then trace the normal asyncio.gather() dispatch path and the existing _fired_or_listeners state. Add a regression test based on the ParallelFanoutOrFlow reproduction; done means both producer branches complete and the downstream OR listener fires exactly once.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.