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 ([email protected])

…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 <[email protected]>
@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 <[email protected]>
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.

1 participant