diff --git a/src/a2a/server/tasks/task_manager.py b/src/a2a/server/tasks/task_manager.py index e5d899c1e..43c79bd5a 100644 --- a/src/a2a/server/tasks/task_manager.py +++ b/src/a2a/server/tasks/task_manager.py @@ -74,12 +74,14 @@ def append_artifact_to_task(task: Task, event: TaskArtifactUpdateEvent) -> None: dict(new_artifact_data.metadata.items()) ) else: - # We received a chunk to append, but we don't have an existing artifact. - # we will ignore this chunk - logger.warning( - 'Received append=True for nonexistent artifact index %s in task %s. Ignoring chunk.', - artifact_id, - task.id, + # We received a chunk to append, but there is no existing artifact + # with this id. Silently dropping the chunk would hide a real bug + # in the caller (e.g. generating a fresh artifact_id on every + # add_artifact call instead of pinning one), so we raise. + raise ValueError( + f'append=True but no artifact with id {artifact_id!r} exists on ' + f'task {task.id!r}. Create the artifact first (append=False) ' + f'before appending to it.' ) diff --git a/tests/server/tasks/test_task_manager.py b/tests/server/tasks/test_task_manager.py index eba8d2f14..d3412dc96 100644 --- a/tests/server/tasks/test_task_manager.py +++ b/tests/server/tasks/test_task_manager.py @@ -429,6 +429,7 @@ def test_append_artifact_to_task(): assert len(task.artifacts[1].parts) == 1 # Test appending part to a task that does not have a matching artifact + # raises ValueError instead of silently dropping the chunk (#1038) non_existing_artifact_with_parts = Artifact( artifact_id='artifact-456', parts=[Part(text='Part 1')] ) @@ -438,7 +439,9 @@ def test_append_artifact_to_task(): task_id='123', context_id='123', ) - append_artifact_to_task(task, append_event_5) + with pytest.raises(ValueError, match='append=True but no artifact with id'): + append_artifact_to_task(task, append_event_5) + # Artifacts unchanged — the bad chunk was not silently added assert len(task.artifacts) == 2 assert len(task.artifacts[0].parts) == 2 assert len(task.artifacts[1].parts) == 1