Skip to content

fix: detect circular task context dependencies#6441

Open
samrusani wants to merge 1 commit into
crewAIInc:mainfrom
samrusani:fix/task-context-cycle-validation
Open

fix: detect circular task context dependencies#6441
samrusani wants to merge 1 commit into
crewAIInc:mainfrom
samrusani:fix/task-context-cycle-validation

Conversation

@samrusani

Copy link
Copy Markdown

Summary

  • detect circular task context dependencies when a Crew is constructed
  • report the exact cycle path so users get a clear ValueError instead of a runtime recursion/hang
  • add regression coverage for Task 1 -> Task 2 -> Task 3 -> Task 1

Fixes #6433

Testing

  • uv run pytest lib/crewai/tests/test_crew.py::test_context_no_circular_dependencies lib/crewai/tests/test_crew.py::test_context_no_future_tasks -q
  • uv run ruff check lib/crewai/src/crewai/crew.py lib/crewai/tests/test_crew.py
  • uv run ruff format lib/crewai/src/crewai/crew.py lib/crewai/tests/test_crew.py
  • git diff --check

Notes

Authored with AI assistance. Please keep/apply the llm-generated label per the contribution guide.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Pydantic model validator to the Crew class that detects circular dependencies among Task.context lists using DFS with object identity tracking, raising a ValueError with the dependency chain when a cycle is found. Includes a regression test verifying the new behavior.

Changes

Circular Context Dependency Detection

Layer / File(s) Summary
Cycle detection validator
lib/crewai/src/crewai/crew.py
Adds validate_context_no_circular_dependencies model validator that performs DFS over task context lists using object identity, tracking a recursion stack and path, raising ValueError with the cycle chain if detected; runs before validate_context_no_future_tasks.
Regression test
lib/crewai/tests/test_crew.py
Adds test_context_no_circular_dependencies, constructing three tasks with chained context references forming a cycle and asserting Crew creation raises ValueError with the expected dependency chain message.

Sequence Diagram(s)

sequenceDiagram
    participant Developer
    participant Crew
    participant Validator as validate_context_no_circular_dependencies
    participant Task

    Developer->>Crew: create Crew(tasks=[...])
    Crew->>Validator: run model validator (mode="after")
    Validator->>Task: read task.context lists
    loop DFS over task graph
        Validator->>Validator: track visiting stack and path
        alt cycle detected
            Validator->>Crew: raise ValueError(dependency chain)
        else no cycle
            Validator->>Validator: mark task visited
        end
    end
    Validator-->>Crew: return Self
    Crew-->>Developer: Crew instance or ValueError
Loading

Compact metadata

  • Type: Bug fix (validation added at construction time)
  • Files changed: 2 (lib/crewai/src/crewai/crew.py, lib/crewai/tests/test_crew.py)
  • Lines changed: +72/-0

Related issues

  • #6433: fix: Task dependency graph should validate cycles at creation time, not at runtime

Related PRs

  • None identified

Suggested labels

  • bug, validation, crew

Suggested reviewers

  • None identified

Poem
A rabbit hopped through task and chain,
Round and round, then stopped the strain,
"Task A needs B, B needs C,
C needs A — no way!" said he.
Now cycles halt before they run,
Caught at birth, the loop undone. 🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: detecting circular task context dependencies.
Description check ✅ Passed The description is directly related to the code changes and summarizes the new cycle detection and regression test.
Linked Issues check ✅ Passed The PR satisfies #6433 by detecting cycles at crew creation, raising a clear ValueError, and adding coverage for a cycle.
Out of Scope Changes check ✅ Passed The changes stay within scope, limited to cycle validation logic and its regression test.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@samrusani

Copy link
Copy Markdown
Author

I do not have permission to apply repository labels from this fork PR, but this PR was authored with AI assistance. Please apply the required llm-generated label per the contribution guide.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/crewai/src/crewai/crew.py (1)

838-878: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Recursive DFS can itself hit RecursionError on long (acyclic) context chains.

The cycle-detection algorithm itself is correct, but visit() recurses once per context edge. This PR's stated goal is to replace RecursionError/hangs with a clear ValueError (issue #6433), yet a sufficiently long non-cyclic dependency chain (approaching Python's default recursion limit, ~1000) would still raise RecursionError here — before the validator even reports the actual cycle, if one exists elsewhere. Converting to an iterative DFS (explicit stack) removes this residual risk without changing the detection semantics.

♻️ Suggested iterative rewrite
     `@model_validator`(mode="after")
     def validate_context_no_circular_dependencies(self) -> Self:
         """Validates that task context dependencies do not contain cycles."""
         task_ids = {id(task) for task in self.tasks}
-        visiting: set[int] = set()
         visited: set[int] = set()
-        path: list[Task] = []
-
-        def visit(task: Task) -> None:
-            task_id = id(task)
-            if task_id in visited:
-                return
-            if task_id in visiting:
-                cycle_start = next(
-                    index
-                    for index, path_task in enumerate(path)
-                    if id(path_task) == task_id
-                )
-                cycle_tasks = [*path[cycle_start:], task]
-                cycle_descriptions = " -> ".join(
-                    cycle_task.description for cycle_task in cycle_tasks
-                )
-                raise ValueError(
-                    "Task context dependencies contain a circular dependency: "
-                    f"{cycle_descriptions}."
-                )
-
-            visiting.add(task_id)
-            path.append(task)
-            if isinstance(task.context, list):
-                for context_task in task.context:
-                    if id(context_task) in task_ids:
-                        visit(context_task)
-            path.pop()
-            visiting.remove(task_id)
-            visited.add(task_id)
 
         for task in self.tasks:
-            visit(task)
+            if id(task) in visited:
+                continue
+            path: list[Task] = []
+            on_path: dict[int, int] = {}
+            stack: list[tuple[Task, Any]] = [(task, iter(task.context or []))]
+            on_path[id(task)] = 0
+            path.append(task)
+            while stack:
+                current, it = stack[-1]
+                advanced = False
+                for context_task in it:
+                    if id(context_task) not in task_ids:
+                        continue
+                    cid = id(context_task)
+                    if cid in on_path:
+                        cycle_tasks = [*path[on_path[cid] :], context_task]
+                        cycle_descriptions = " -> ".join(
+                            t.description for t in cycle_tasks
+                        )
+                        raise ValueError(
+                            "Task context dependencies contain a circular dependency: "
+                            f"{cycle_descriptions}."
+                        )
+                    if cid not in visited:
+                        on_path[cid] = len(path)
+                        path.append(context_task)
+                        stack.append(
+                            (context_task, iter(context_task.context or []))
+                        )
+                        advanced = True
+                        break
+                if not advanced:
+                    finished, _ = stack.pop()
+                    path.pop()
+                    del on_path[id(finished)]
+                    visited.add(id(finished))
         return self
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/crew.py` around lines 838 - 878, The recursive DFS in
validate_context_no_circular_dependencies on Crew can still raise RecursionError
for long acyclic task.context chains before it reports the intended ValueError.
Replace the nested visit() recursion with an iterative DFS using an explicit
stack while preserving the current visiting/visited/path cycle-detection
behavior and the same circular-dependency error message. Keep the logic scoped
around Crew.tasks and Task.context so the validator still detects cycles without
depending on Python recursion depth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/crewai/src/crewai/crew.py`:
- Around line 838-878: The recursive DFS in
validate_context_no_circular_dependencies on Crew can still raise RecursionError
for long acyclic task.context chains before it reports the intended ValueError.
Replace the nested visit() recursion with an iterative DFS using an explicit
stack while preserving the current visiting/visited/path cycle-detection
behavior and the same circular-dependency error message. Keep the logic scoped
around Crew.tasks and Task.context so the validator still detects cycles without
depending on Python recursion depth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ede7cde-a453-4e47-a96e-433d7e8470cc

📥 Commits

Reviewing files that changed from the base of the PR and between 2b90117 and 340b897.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/crew.py
  • lib/crewai/tests/test_crew.py

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.

fix: Task dependency graph should validate cycles at creation time, not at runtime

1 participant