Skip to content

Conversation

@devin-ai-integration
Copy link
Contributor

Fix asyncio hanging issue when using crew.kickoff() with asyncio.to_thread()

Summary

Fixes issue #3730 where crews would hang indefinitely when run with asyncio.to_thread(crew.kickoff) if they used async tools.

Root Cause: The tools code was calling asyncio.run() to execute async tool functions. When a crew is run via asyncio.to_thread(), it executes in a thread spawned by an existing event loop. Calling asyncio.run() in this context fails because it tries to create a new event loop in a thread that's conceptually part of an already-running loop, causing a hang.

Solution: Added a run_coroutine_sync() utility that:

  • Uses asyncio.run() when no event loop is running (normal case)
  • When a loop IS running, spawns a new thread with its own event loop to run the coroutine

Changes:

  • Added src/crewai/utilities/asyncio_utils.py with run_coroutine_sync() helper
  • Updated CrewStructuredTool.invoke() to use the new helper (2 call sites)
  • Updated BaseTool.run() to use the new helper (1 call site)
  • Added comprehensive test suite in tests/test_asyncio_tools.py

Review & Testing Checklist for Human

⚠️ Risk Level: MEDIUM-HIGH - This modifies a core execution path affecting all async tool usage

  • Review the threading approach in run_coroutine_sync() - Is spawning a new thread with a new event loop the right solution? Consider:

    • Performance implications (creates a thread for every async tool call when in nested async context)
    • Potential thread safety issues
    • Resource leak risks (event loop cleanup)
    • Whether there's a better alternative (e.g., using asyncio.get_event_loop().run_until_complete() or other approaches)
  • Test the actual issue scenario end-to-end - The new tests mock Agent.execute_task, so they don't verify full integration. Test manually:

    # Run this with a real LLM configured
    import asyncio
    from crewai import Agent, Crew, Task
    from crewai.tools import tool
    
    @tool
    async def test_tool(x: str) -> str:
        """Test async tool"""
        await asyncio.sleep(0.1)
        return f"Result: {x}"
    
    crew = Crew(
        agents=[Agent(role="Test", goal="Test", backstory="Test")],
        tasks=[Task(description="Use test_tool", expected_output="Result", tools=[test_tool])]
    )
    
    async def run():
        result = await asyncio.to_thread(crew.kickoff)
        print(result)
    
    asyncio.run(run())

    Verify it completes without hanging.

  • Verify the lock file regeneration didn't break anything - The uv.lock was completely regenerated (not part of core fix). Run the full test suite to ensure no regressions.

  • Check exception handling - Verify that exceptions raised in async tools are properly propagated with full tracebacks through the new threading approach.

Notes

  • Issue reported by user with local Ollama setup experiencing hangs when running crew in background with asyncio.to_thread()
  • The existing kickoff_async() method already worked correctly; this fixes the manual asyncio.to_thread(crew.kickoff) pattern
  • All new tests pass, and existing thread safety tests pass

Link to Devin run: https://app.devin.ai/sessions/fa61a1f74deb410faa495932f4a86cad
Requested by: João (joao@crewai.com)

…hread()

This fix resolves issue #3730 where crews would hang when run with
asyncio.to_thread() if they used async tools. The problem was that
asyncio.run() was being called in tool execution code, which fails
when there's already an event loop running in the current thread.

Changes:
- Added run_coroutine_sync() utility function that safely runs
  coroutines in both sync and async contexts
- Updated CrewStructuredTool.invoke() to use run_coroutine_sync()
- Updated BaseTool.run() to use run_coroutine_sync()
- Added comprehensive tests for asyncio tool execution scenarios

The fix ensures that async tools work correctly whether the crew is:
- Run synchronously with crew.kickoff()
- Run asynchronously with crew.kickoff_async()
- Run in a thread pool with asyncio.to_thread(crew.kickoff)

Fixes #3730

Co-Authored-By: João <joao@crewai.com>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring
- Remove trailing whitespace from blank lines
- Remove unused loop variable

Co-Authored-By: João <joao@crewai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant