fix: sync kickoff-completed event with OUTPUT hook result (#6571)

* fix: sync kickoff-completed event with OUTPUT hook result

`CrewKickoffCompletedEvent` still carried the pre-hook `TaskOutput`, so
AMP/OTEL consumers never saw `OUTPUT` mutations even though the returned
`CrewOutput` was updated. Sync `final_task_output.raw` from the post-hook
payload before emit, matching `FlowFinishedEvent`.

* style: drop OUTPUT sync comment and rename crew output test
This commit is contained in:
Lucas Gomide
2026-07-16 15:37:56 -03:00
committed by GitHub
parent 5dba2ef623
commit 9a49af098b
2 changed files with 37 additions and 0 deletions

View File

@@ -1928,6 +1928,9 @@ class Crew(FlowTrackable, BaseModel):
dispatch(InterceptionPoint.EXECUTION_END, end_ctx)
crew_output = cast(CrewOutput, end_ctx.payload)
if isinstance(crew_output, CrewOutput):
final_task_output.raw = crew_output.raw
# Ensure background memory saves finish (and emit their
# completed/failed events) before the kickoff-completed event below
# triggers listener teardown/finalization.

View File

@@ -10,6 +10,9 @@ from __future__ import annotations
from unittest.mock import patch
from crewai.agent import Agent
from crewai.crew import Crew
from crewai.events.event_bus import crewai_event_bus
from crewai.events.types.crew_events import CrewKickoffCompletedEvent
from crewai.flow.flow import Flow, listen, start
from crewai.hooks.dispatch import (
HookAborted,
@@ -147,3 +150,34 @@ class TestTaskStepPoints:
assert result.raw == "sanitized output"
assert (tmp_path / "output.txt").read_text() == "sanitized output"
class TestCrewOutput:
def test_output_modification_reaches_kickoff_completed_event(self):
@on(InterceptionPoint.OUTPUT)
def append_notice(ctx):
if hasattr(ctx.payload, "raw") and isinstance(ctx.payload.raw, str):
ctx.payload.raw += "\nchanged by hook"
return None
completed_raw: list[str] = []
@crewai_event_bus.on(CrewKickoffCompletedEvent)
def capture_completed(_source, event: CrewKickoffCompletedEvent):
completed_raw.append(event.output.raw)
agent = Agent(role="Writer", goal="Write", backstory="Writes things.")
task = Task(
description="Write something",
expected_output="Some text",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task], verbose=False)
with patch.object(Agent, "execute_task", return_value="original output"):
result = crew.kickoff()
crewai_event_bus.flush()
assert result.raw.endswith("changed by hook")
assert completed_raw
assert completed_raw[-1].endswith("changed by hook")