This is an automated email from the ASF dual-hosted git repository.

kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 1f4fe796933 Keep the pydantic-ai first-run banner out of Common AI 
task logs (#74900)
1f4fe796933 is described below

commit 1f4fe7969334be3592db6509b36a38e4782dcffc
Author: Kaxil Naik <[email protected]>
AuthorDate: Sat Oct 17 13:36:39 2026 +0100

    Keep the pydantic-ai first-run banner out of Common AI task logs (#74900)
---
 providers/common/ai/docs/frameworks/pydantic_ai.rst    |  6 ++++++
 .../airflow/providers/common/ai/hooks/pydantic_ai.py   |  5 +++++
 .../ai/tests/unit/common/ai/hooks/test_pydantic_ai.py  | 18 ++++++++++++++++++
 3 files changed, 29 insertions(+)

diff --git a/providers/common/ai/docs/frameworks/pydantic_ai.rst 
b/providers/common/ai/docs/frameworks/pydantic_ai.rst
index 0ae4bd8cf98..67e6f6b27c6 100644
--- a/providers/common/ai/docs/frameworks/pydantic_ai.rst
+++ b/providers/common/ai/docs/frameworks/pydantic_ai.rst
@@ -60,6 +60,12 @@ supplied the tool. To get that masking on an agent you build 
with ``Agent(...)``
 
     agent = Agent(model, toolsets=[my_toolset], 
capabilities=[MaskingCapability()])
 
+Pydantic AI 2.43 and later print a first-run banner to stderr when Airflow was 
started from
+a coding agent such as Claude Code, Cursor or Codex, and a task's stderr lands 
in its log
+as ``ERROR`` lines. Importing ``PydanticAIHook``, as the example above does, 
turns the
+banner off for the task process; for an agent run without the hook, set
+``PYDANTIC_AI_NO_BANNER=1`` in the worker environment.
+
 What you get, and what you do not
 ---------------------------------
 
diff --git 
a/providers/common/ai/src/airflow/providers/common/ai/hooks/pydantic_ai.py 
b/providers/common/ai/src/airflow/providers/common/ai/hooks/pydantic_ai.py
index 3a6a91404d8..146560881e4 100644
--- a/providers/common/ai/src/airflow/providers/common/ai/hooks/pydantic_ai.py
+++ b/providers/common/ai/src/airflow/providers/common/ai/hooks/pydantic_ai.py
@@ -20,6 +20,7 @@ import re
 from pathlib import Path
 from typing import TYPE_CHECKING, Any, TypeVar, overload
 
+import pydantic_ai
 from pydantic_ai import Agent
 from pydantic_ai.exceptions import ModelAPIError
 from pydantic_ai.models import infer_model
@@ -31,6 +32,10 @@ from airflow.providers.common.ai.utils.toolset_base import 
MaskingCapability
 from airflow.providers.common.compat.module_loading import import_string
 from airflow.providers.common.compat.sdk import BaseHook
 
+# An Airflow task is never interactive, and pydantic-ai's first-run banner on 
stderr would
+# land in the task log as ERROR lines.
+pydantic_ai.BANNER_ENABLED = False
+
 OutputT = TypeVar("OutputT")
 
 # Sentinel distinguishing "caller did not pass ``instrument``" from an explicit
diff --git a/providers/common/ai/tests/unit/common/ai/hooks/test_pydantic_ai.py 
b/providers/common/ai/tests/unit/common/ai/hooks/test_pydantic_ai.py
index 098408e02d6..3aa297c3bd4 100644
--- a/providers/common/ai/tests/unit/common/ai/hooks/test_pydantic_ai.py
+++ b/providers/common/ai/tests/unit/common/ai/hooks/test_pydantic_ai.py
@@ -1338,6 +1338,24 @@ class TestPydanticAIHookCreateAgent:
 
         assert agent.run_sync("hi").output == "api key: ***"
 
+    def test_agent_run_from_a_coding_agent_shell_prints_no_banner(self, 
monkeypatch, capsys):
+        """pydantic-ai shows its first-run banner to a coding agent; a task's 
stderr goes to the task log."""
+        # The banner, and the module holding its once-per-process claim, 
arrived in pydantic-ai 2.43.
+        pydantic_ai_display = pytest.importorskip("pydantic_ai._display")
+        monkeypatch.setenv("CLAUDECODE", "1")
+        # pydantic-ai suppresses the banner by itself under pytest and in CI.
+        monkeypatch.delenv("PYTEST_VERSION", raising=False)
+        monkeypatch.delenv("CI", raising=False)
+        monkeypatch.delenv("PYDANTIC_AI_NO_BANNER", raising=False)
+        monkeypatch.setattr(pydantic_ai_display, "_banner_displayed", False)
+
+        hook = PydanticAIHook(llm_conn_id="test_conn")
+        with patch.object(hook, "get_conn", autospec=True, 
return_value=TestModel()):
+            agent = hook.create_agent(instructions="hi")
+        agent.run_sync("hi")
+
+        assert capsys.readouterr().err == ""
+
     def test_create_agent_without_instructions_or_spec_file_raises(self):
         hook = PydanticAIHook(llm_conn_id="test_conn", 
model_id="openai:gpt-5.6-sol")
         with pytest.raises(ValueError, match="instructions is required"):

Reply via email to