Skip to content

fix(crew): await async before/after_kickoff_callbacks in akickoff#6500

Open
nolanchic wants to merge 1 commit into
crewAIInc:mainfrom
nolanchic:fix/akickoff-async-callbacks
Open

fix(crew): await async before/after_kickoff_callbacks in akickoff#6500
nolanchic wants to merge 1 commit into
crewAIInc:mainfrom
nolanchic:fix/akickoff-async-callbacks

Conversation

@nolanchic

Copy link
Copy Markdown

Description

Crew.akickoff() is a native async path, but before_kickoff_callbacks and after_kickoff_callbacks were invoked without any awaitable check — silently dropping async callables:

  • after_kickoff_callbacks: result = after_callback(result) returned an unawaited coroutine when the callback was async, so the crew result was silently replaced with a coroutine object (never executed, never awaited).
  • before_kickoff_callbacks: invoked inside the synchronous prepare_kickoff(), which has no async support — async before-callbacks never ran, and any blocking I/O inside a sync before-callback stalled the event loop.

This is inconsistent with task_callback and step_callback, which correctly check inspect.isawaitable in the async path.

Changes

  • crews/utils.py: add async def aprepare_kickoff(...) — the async counterpart of prepare_kickoff. It awaits before-callbacks that return an awaitable (sync callbacks run unchanged). The shared body is extracted into _prepare_kickoff_impl(...) so the two entry points stay DRY and behavior is identical apart from callback awaiting.
  • crew.py: akickoff now calls await aprepare_kickoff(...) instead of prepare_kickoff(...), and awaits each after-callback result when it is awaitable.

Behavior preserved

  • kickoff() (sync) — unchanged, still uses prepare_kickoff.
  • kickoff_async() — unchanged; it wraps the sync kickoff in asyncio.to_thread, so all callbacks already run off the event loop.
  • Sync callbacks in akickoff — unchanged (an isawaitable check is a no-op for them).

Test plan

  • test_akickoff_awaits_async_after_callbacks — async after-callback is awaited; result stays a CrewOutput (was a coroutine before).
  • test_akickoff_applies_async_after_callback_result — async after-callback's returned CrewOutput replaces the result.
  • test_akickoff_awaits_async_before_callbacks — async before-callback is awaited and runs.
  • test_akickoff_applies_async_before_callback_inputs — async before-callback's returned inputs flow into the crew.
  • uv run pytest lib/crewai/tests/crew/test_async_crew.py lib/crewai/tests/test_crew.py lib/crewai/tests/test_checkpoint.py → all pass (sync kickoff + kickoff_async callbacks unaffected).
  • uv run ruff check lib/ → clean.
  • uv run mypy lib/crewai/src/crewai/crew.py lib/crewai/src/crewai/crews/utils.py → clean (strict).

Fixes #6481.

Per .github/CONTRIBUTING.md, this PR is authored by an AI agent (Claude Code) and should carry the llm-generated label (external contributors can't self-apply labels).

akickoff invoked before/after_kickoff_callbacks without an awaitable
check, so async callables were silently dropped: an async after-callback
replaced the crew result with an unawaited coroutine, and async
before-callbacks never ran because prepare_kickoff is synchronous.

- Add aprepare_kickoff (async counterpart of prepare_kickoff) that
  awaits callbacks returning an awaitable, matching task/step_callback
  handling. Shared body extracted into _prepare_kickoff_impl to avoid
  duplication.
- In akickoff, await the result of each after_kickoff_callback when it
  is awaitable.

Sync kickoff and kickoff_async (to_thread wrapper) are unchanged.

Fixes crewAIInc#6481
@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: 2a9547ab-0b0e-484e-a4eb-967caf0096ff

📥 Commits

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

📒 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

📝 Walkthrough

Walkthrough

This PR adds async-aware handling for before_kickoff_callbacks and after_kickoff_callbacks in Crew.akickoff(). It introduces aprepare_kickoff and refactors shared logic into _prepare_kickoff_impl, awaits async before-callbacks, and awaits async after-callback results, with new regression tests validating both flows.

Changes

Async kickoff callback support

Layer / File(s) Summary
Async before-callback preparation
lib/crewai/src/crewai/crews/utils.py
prepare_kickoff now delegates to a shared _prepare_kickoff_impl; a new aprepare_kickoff awaits async before_kickoff_callbacks via _arun_before_kickoff_callbacks, avoiding double execution across sync/async entry points.
Crew.akickoff() wiring
lib/crewai/src/crewai/crew.py
Imports inspect and aprepare_kickoff; akickoff() now awaits aprepare_kickoff(...) for input preparation and awaits after_kickoff_callbacks results when they are awaitable.
Regression tests
lib/crewai/tests/crew/test_async_crew.py
New TestAsyncCrewKickoff tests verify async after-callbacks are awaited and their CrewOutput used, and async before-callbacks are awaited with merged inputs flowing into task execution.

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: akickoff(inputs)
  Crew->>Utils: await aprepare_kickoff(inputs)
  Utils->>BeforeCB: await callback(inputs)
  BeforeCB-->>Utils: normalized inputs
  Utils-->>Crew: prepared state
  Crew->>Crew: execute tasks
  Crew->>AfterCB: after_callback(result)
  AfterCB-->>Crew: result (possibly coroutine)
  Crew->>Crew: if isawaitable(result): await result
  Crew-->>Caller: CrewOutput
Loading

Suggested reviewers: joaomdmoura

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: awaiting async kickoff callbacks in akickoff.
Description check ✅ Passed The description matches the code changes and explains the async before/after kickoff callback fix accurately.
Linked Issues check ✅ Passed The PR satisfies #6481 by adding async before-kickoff support and awaiting async after-kickoff callbacks in akickoff.
Out of Scope Changes check ✅ Passed The changed files and tests are focused on the callback async kickoff fix and do not दिख suggest unrelated scope.
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.

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

1 participant