Skip to content

fix(crewai): prevent span loss from out-of-order callbacks - #294

Merged
pradystar merged 4 commits into
mainfrom
fix/sao-19050-crewai-callback-ordering
Oct 26, 2026
Merged

pradystar merged 4 commits into
mainfrom
fix/sao-19050-crewai-callback-ordering

Conversation

@pradystar

Copy link
Copy Markdown
Collaborator

Summary

Fixes SAO-19050.

CrewAI dispatches synchronous event listeners through a thread pool, allowing completion callbacks to execute before their matching starts. This caused dropped spans and incomplete hierarchies.

  • Use CrewAI’s async listener path without yielding during lifecycle updates.
  • Isolate handler state and SDK/OTel context per crew invocation.
  • Match LLM parents to the agent run IDs used by older CrewAI versions.
  • Clean up state after completion, failure, or malformed callbacks.

Runtime changes are limited to the CrewAI handler. No dependency, base-handler, logger, decorator, or native OTel changes.

Validation

  • 415 tests passed, 1 skipped across CrewAI and compatibility suites.
  • Ruff lint/format and configured type checks passed.
  • Deterministic sync/async examples passed on CrewAI 1.6.1 and 1.15.28.
  • Each example produced 21 spans across 3 traces with correct hierarchy and clean context.
  • All 84 recorded spans included gen_ai.operation.name.

@pradystar
pradystar marked this pull request as ready for review October 23, 2026 21:05

@etserend etserend left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 This review was generated by the Astra agent (claude-opus-5-5). It may contain mistakes.

Findings below were re-validated by reproducing them against lab0 (CrewAI 1.6.1) on this PR's head 8f4ce0c and its base.

Verdict: request_changes — Overlapping crews now get separate roots, but the change introduces four regressions and leaves one ticket criterion unmet.

Findings

  • 🟠 major (bug): Child error tears down the whole crew. See the inline comment on handler.py:190.
  • 🟠 major (bug): LiteLLM token usage is lost for overlapping crews. Only the first crew's chat span gets usage (10/5/15). The second crew's has none. lite_llm_usage_callback (handler.py:881-898) looks nodes up only in self._handler, but later crews use their own handlers. Look the node up across run.handler for every run in _crew_runs.
  • 🟡 minor (bug): Events outside any crew are dropped. See the inline comment on handler.py:171.
  • 🟡 minor (performance): The root flush blocks CrewAI's shared events loop. See the inline comment on handler.py:128.
  • 🟡 minor (question): Original event timestamps are ignored. SAO-19050 requires "original event timestamps". Events stamped 10 minutes earlier produce spans that start at run time (+600 s) and last under 1 s instead of 8 s. event.timestamp goes only into metadata (handler.py:462), while created_at and duration_ns come from callback time (base_handler.py:543-544, :616). The base behaves the same, so this is not a regression. Is it in scope? If so, pass created_at=event.timestamp on start and derive the duration from the end event's timestamp.
  • 🟡 minor (testing): The ticket asks for deterministic tests of reversed lifecycle delivery, parent/child reordering, duplicates, failure and cleanup. Failure and cleanup are covered. No test sends a duplicated start or end event, and the reversed-delivery branch in the crewai_bus fixture never runs (see the inline comment on tests/test_crewai_handler.py:158). Caller-context inheritance across the CrewAI loop thread works (confirmed on lab0), but no test locks it in. Suggested additions:
    1. Run a real asyncio loop on a separate thread, emit with asyncio.run_coroutine_threadsafe from the test thread as CrewAI does, and assert that caller OTel and logger context are inherited.
    2. Add one test with a duplicated TaskStartedEvent and one with a duplicated TaskCompletedEvent. Assert that each span appears exactly once and that no state remains.

Comment thread tests/test_crewai_handler.py Resolved
@pradystar

Copy link
Copy Markdown
Collaborator Author

Addressed all focused comments and follow-ups, with regression coverage

@etserend etserend left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 This review was generated by the Astra agent (claude-opus-5-5). It may contain mistakes.

Verdict: request_changes — A major data-loss race from an earlier local round is still present at HEAD 254fb2d: ingestion-hook flushes can drop another crew's trace.

Addressed before this review

  • The deferred async_flush() in ingestion-hook batch mode could drop another crew's open trace. _flush_batch yields during the hook, then clears self.traces. Not addressed: HEAD is still 254fb2d, the commit the concern was raised against. It is raised again in this review.

@pradystar
pradystar merged commit e3c4934 into main Oct 26, 2026
13 checks passed
@pradystar
pradystar deleted the fix/sao-19050-crewai-callback-ordering branch October 26, 2026 16:58
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants