Repository navigation
fix(crewai): prevent span loss from out-of-order callbacks - #294
Conversation
There was a problem hiding this comment.
🤖 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
chatspan gets usage (10/5/15). The second crew's has none.lite_llm_usage_callback(handler.py:881-898) looks nodes up only inself._handler, but later crews use their own handlers. Look the node up acrossrun.handlerfor 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.timestampgoes only into metadata (handler.py:462), whilecreated_atandduration_nscome 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, passcreated_at=event.timestampon 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_busfixture never runs (see the inline comment ontests/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:- Run a real
asyncioloop on a separate thread, emit withasyncio.run_coroutine_threadsafefrom the test thread as CrewAI does, and assert that caller OTel and logger context are inherited. - Add one test with a duplicated
TaskStartedEventand one with a duplicatedTaskCompletedEvent. Assert that each span appears exactly once and that no state remains.
- Run a real
|
Addressed all focused comments and follow-ups, with regression coverage |
etserend
left a comment
There was a problem hiding this comment.
🤖 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_batchyields during the hook, then clearsself.traces. Not addressed: HEAD is still 254fb2d, the commit the concern was raised against. It is raised again in this review.
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.
Runtime changes are limited to the CrewAI handler. No dependency, base-handler, logger, decorator, or native OTel changes.
Validation
gen_ai.operation.name.