Skip to content

fix: support async callables in akickoff before/after callbacks#6494

Open
ashusnapx wants to merge 1 commit into
crewAIInc:mainfrom
ashusnapx:feat/async-callback-fix
Open

fix: support async callables in akickoff before/after callbacks#6494
ashusnapx wants to merge 1 commit into
crewAIInc:mainfrom
ashusnapx:feat/async-callback-fix

Conversation

@ashusnapx

Copy link
Copy Markdown

Summary

Fixes silent discarding of async callables in Crew.akickoff().

Closes #6481

Problem

Crew.akickoff() is a native async execution path, but before_kickoff_callbacks and after_kickoff_callbacks were invoked synchronously with no inspect.isawaitable check. Async callables were silently discarded — their coroutine objects were never awaited.

async def my_callback(result):
    await some_async_operation()  # never awaited
    return result

crew = Crew(..., after_kickoff_callbacks=[my_callback])
await crew.akickoff()  # callback silently discarded

Fix

Component Change
after_kickoff_callbacks in akickoff() Added inspect.isawaitable check + await
before_kickoff_callbacks in akickoff() Created aprepare_kickoff() async variant that awaits async callbacks

Follows the same pattern used by task_callback and step_callback throughout the codebase:

cb_result = callback(result)
if inspect.isawaitable(cb_result):
    await cb_result

Files Changed

File Change
crew.py Added import inspect, fixed after_callback loop, imports aprepare_kickoff
crews/utils.py Added aprepare_kickoff() async variant with inspect.isawaitable for before_callbacks
tests/crew/test_async_crew.py 3 new tests: async before, async after, mixed sync+async

Tests

# Run async crew tests
pytest lib/crewai/tests/crew/test_async_crew.py -v

# Run all crew tests
pytest lib/crewai/tests/test_crew.py lib/crewai/tests/crew/ -v
  • 14 async crew tests pass (3 new)
  • ruff: all checks passed
  • mypy: no issues found

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a2547b74-bcc3-4d75-8abe-b3b1624f8f5a

📥 Commits

Reviewing files that changed from the base of the PR and between 04c4ced and 3cfba59.

📒 Files selected for processing (3)
  • lib/crewai/src/crewai/crew.py
  • lib/crewai/src/crewai/crews/utils.py
  • lib/crewai/tests/crew/test_async_crew.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/crewai/src/crewai/crew.py
  • lib/crewai/tests/crew/test_async_crew.py

📝 Walkthrough

Walkthrough

Adds asynchronous kickoff preparation in crews/utils.py, updates Crew.akickoff to await before and after callback results, and extends async kickoff tests for callback completion and ordering.

Changes

Async before/after kickoff callback support

Layer / File(s) Summary
Shared and asynchronous kickoff preparation
lib/crewai/src/crewai/crews/utils.py
Extracts shared kickoff setup helpers, preserves synchronous preparation, and adds aprepare_kickoff with awaitable before_kickoff_callbacks support.
Native async kickoff integration
lib/crewai/src/crewai/crew.py
Uses awaited asynchronous preparation in akickoff and awaits awaitable after_kickoff_callbacks results.
Async callback validation
lib/crewai/tests/crew/test_async_crew.py
Adds tests verifying async before/after callbacks are awaited and mixed callback types execute in the expected order.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Crew as Crew.akickoff
  participant Utils as aprepare_kickoff
  participant BeforeCB as before_kickoff_callbacks
  participant AfterCB as after_kickoff_callbacks

  Caller->>Crew: await akickoff(inputs)
  Crew->>Utils: await aprepare_kickoff(crew, inputs)
  Utils->>BeforeCB: invoke callback chain
  BeforeCB-->>Utils: result or awaitable
  Utils->>Utils: await awaitable result
  Utils-->>Crew: normalized inputs
  Crew->>AfterCB: invoke callback chain
  AfterCB-->>Crew: result or awaitable
  Crew->>Crew: await awaitable result
  Crew-->>Caller: CrewOutput
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: async support for before/after kickoff callbacks in akickoff.
Description check ✅ Passed The description is directly related to the code changes and accurately describes the async callback fix.
Linked Issues check ✅ Passed The changes satisfy #6481 by awaiting async after callbacks and adding an async kickoff prep path for before callbacks.
Out of Scope Changes check ✅ Passed The refactor into shared kickoff helpers supports the async callback fix and does not introduce unrelated changes.
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.

@ashusnapx ashusnapx force-pushed the feat/async-callback-fix branch from eb92e41 to 04c4ced Compare July 9, 2026 10:45

@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.

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@lib/crewai/src/crewai/crews/utils.py`:
- Around line 362-474: The kickoff preparation logic is duplicated between
prepare_kickoff and aprepare_kickoff, creating drift risk as shared behavior
changes over time. Extract the common control flow into a shared internal helper
used by both functions, and keep only the before_kickoff_callbacks execution
different by injecting a callback runner that either returns directly or awaits
awaitable results. Preserve the existing behavior around crewai_event_bus
emission, store_files, _set_tasks_callbacks, setup_agents, and planning in the
shared helper.
- Around line 423-431: The kickoff planning path is still blocking the event
loop because `aprepare_kickoff` ultimately calls `crew._handle_crew_planning()`
synchronously via `CrewPlanner._handle_crew_planning()` and `execute_sync()`.
Update the async kickoff flow in `utils.py` so planning is offloaded to a thread
or handled through a non-blocking async path before returning from `akickoff()`,
using the existing `aprepare_kickoff`, `crew._handle_crew_planning`, and
`CrewPlanner` hooks to locate the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 460ccfbf-0675-415f-a37f-8ca756db70d3

📥 Commits

Reviewing files that changed from the base of the PR and between 289686a and eb92e41.

📒 Files selected for processing (3)
  • lib/crewai/src/crewai/crew.py
  • lib/crewai/src/crewai/crews/utils.py
  • lib/crewai/tests/crew/test_async_crew.py

Comment thread lib/crewai/src/crewai/crews/utils.py Outdated
Comment thread lib/crewai/src/crewai/crews/utils.py Outdated

@ErenAta16 ErenAta16 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.

Same underlying fix as #6500 and #6482 (also open) — the crew.py change here is identical to both. Where this one differs is crews/utils.py: it copy-pastes the full body of prepare_kickoff into a new aprepare_kickoff rather than factoring out the shared setup/teardown logic, which #6500 and #6482 both do via extracted helpers. Functionally correct either way and the test coverage here (including the test_akickoff_mixed_sync_async_callbacks ordering check) is solid, but the duplication means a future change to the shared kickoff-preparation logic would need to be applied in two places instead of one — worth weighing against the other two when picking which of the three to merge.

Closes crewAIInc#6481

Crew.akickoff() is a native async execution path, but
before_kickoff_callbacks and after_kickoff_callbacks were invoked
synchronously with no awaitable check. Async callables were silently
discarded — their coroutine objects were never awaited.

- crew.py: Add inspect.isawaitable check in akickoff after_callback loop
- crews/utils.py: Add aprepare_kickoff() async variant that awaits async
  before_kickoff_callbacks using inspect.isawaitable
- crew.py: akickoff() now calls aprepare_kickoff() instead of
  prepare_kickoff()

Follows the same pattern used by task_callback and step_callback.

Tests: test_akickoff_awaits_async_after_callback,
test_akickoff_awaits_async_before_callback,
test_akickoff_mixed_sync_async_callbacks.
All 14 async crew tests pass. Lint and mypy clean.
@ashusnapx ashusnapx force-pushed the feat/async-callback-fix branch from 04c4ced to 3cfba59 Compare July 11, 2026 16:57
@ashusnapx

Copy link
Copy Markdown
Author

Refactored - extracted shared logic into _prepare_kickoff_common and _run_before_callbacks. Both prepare_kickoff and aprepare_kickoff now call the shared helpers. No more duplication.

@ErenAta16

Copy link
Copy Markdown

Confirmed - prepare_kickoff and aprepare_kickoff now both go through _normalize_inputs and _prepare_kickoff_common, with _run_before_callbacks handling the sync path and the async version inlining its own inspect.isawaitable loop (needed since it has to await mid-loop, so it can't reuse the sync helper as-is). Ran the full test_async_crew.py - 14 passed, including the mixed sync/async callback case. No more full-body duplication between the two entry points. Good refactor.

@ashusnapx

Copy link
Copy Markdown
Author

Thanks for confirming. Ready for maintainer review.

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.

[BUG] before/after_kickoff_callbacks do not support async callables in akickoff

2 participants