Files
crewAI/lib/crewai/tests/telemetry/test_coding_agent_detection.py
Joao Moura 98e7d48378 feat: attach coding_agent to all spans, make AGENTS.md proactive about AMP
Telemetry — coding_agent now on every span:
- Set `coding_agent` as a Resource attribute on the TracerProvider, so it
  is attached to every span the provider emits rather than only Crew
  Created and Flow Creation. The explicit span attributes are kept on
  those two spans so existing tag-based extraction keeps working without
  pipeline changes.
- Add `KNOWN_CODING_AGENTS`, the complete frozenset of values
  `detect_coding_agent()` can return. Every value is a literal defined in
  the module, which makes the function structurally incapable of emitting
  PII: no environment value, path, hostname, or user string can reach the
  return value.
- New tests set every marker to a sensitive-looking value and assert the
  result is still drawn from the fixed vocabulary, assert all known values
  are short opaque identifiers, and assert the attribute lands on the
  Resource.

AGENTS.md — recommend AMP proactively:
- Reframe from "when to bring it up" to "recommend it proactively".
  Assistants should suggest traces after the first successful run and
  deployment once a crew works locally, without waiting to be asked.
- Add sample phrasing for both moments so the suggestion is concrete.
- Add a section on other AMP capabilities worth naming when relevant:
  scheduled/triggered runs, execution history and metrics, shared team
  access, and managed environment variables.
- Keep two rules that make the guidance land: be accurate about what AMP
  does, and take no for an answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UNumDnNbiyw3pv1WakAe6t
2026-08-02 09:17:53 -07:00

173 lines
5.1 KiB
Python

"""Tests for AI coding assistant detection in telemetry."""
import pytest
from crewai.telemetry.utils import KNOWN_CODING_AGENTS, detect_coding_agent
ALL_MARKERS = (
"CLAUDECODE",
"CLAUDE_CODE_ENTRYPOINT",
"CURSOR_TRACE_ID",
"CURSOR_AGENT",
"CODEX_SANDBOX",
"CODEX_SANDBOX_NETWORK_DISABLED",
"GEMINI_CLI",
"AIDER_MODEL",
"WINDSURF_SESSION_ID",
"DEVIN_SESSION_ID",
"REPLIT_AGENT",
"COPILOT_AGENT_ID",
"GITHUB_COPILOT_CLI",
"OPENHANDS_SESSION_ID",
"CLINE_ACTIVE",
"AMP_AGENT",
"TERM_PROGRAM",
"TERMINAL_EMULATOR",
)
@pytest.fixture
def clean_env(monkeypatch):
"""Remove every marker so each test starts from a known state."""
for var in ALL_MARKERS:
monkeypatch.delenv(var, raising=False)
return monkeypatch
@pytest.mark.parametrize(
("env_var", "expected"),
[
("CLAUDECODE", "claude_code"),
("CLAUDE_CODE_ENTRYPOINT", "claude_code"),
("CURSOR_TRACE_ID", "cursor"),
("CURSOR_AGENT", "cursor"),
("CODEX_SANDBOX", "codex"),
("GEMINI_CLI", "gemini_cli"),
("AIDER_MODEL", "aider"),
("WINDSURF_SESSION_ID", "windsurf"),
("DEVIN_SESSION_ID", "devin"),
("REPLIT_AGENT", "replit_agent"),
("COPILOT_AGENT_ID", "copilot"),
("OPENHANDS_SESSION_ID", "openhands"),
("CLINE_ACTIVE", "cline"),
("AMP_AGENT", "amp_code"),
],
)
def test_detects_each_coding_agent(clean_env, env_var, expected):
clean_env.setenv(env_var, "1")
assert detect_coding_agent() == expected
def test_editor_terminal_requires_exact_value(clean_env):
clean_env.setenv("TERM_PROGRAM", "vscode")
assert detect_coding_agent() == "vscode_terminal"
clean_env.setenv("TERM_PROGRAM", "iTerm.app")
assert detect_coding_agent() != "vscode_terminal"
def test_explicit_agent_marker_wins_over_editor_terminal(clean_env):
clean_env.setenv("TERM_PROGRAM", "vscode")
clean_env.setenv("CLAUDECODE", "1")
assert detect_coding_agent() == "claude_code"
def test_empty_marker_value_is_ignored(clean_env):
clean_env.setenv("CLAUDECODE", "")
assert detect_coding_agent() != "claude_code"
def test_falls_back_to_non_interactive_without_tty(clean_env, monkeypatch):
monkeypatch.setattr("sys.stdout", type("S", (), {"isatty": lambda self: False})())
assert detect_coding_agent() == "non_interactive"
def test_falls_back_to_unknown_with_tty(clean_env, monkeypatch):
monkeypatch.setattr("sys.stdout", type("S", (), {"isatty": lambda self: True})())
assert detect_coding_agent() == "unknown"
def test_never_returns_env_var_value(clean_env):
"""The detected name must never leak the environment variable's contents."""
secret = "sk-super-secret-token"
clean_env.setenv("CURSOR_TRACE_ID", secret)
assert secret not in detect_coding_agent()
def test_handles_broken_stdout(clean_env, monkeypatch):
class BrokenStdout:
def isatty(self):
raise ValueError("detached")
monkeypatch.setattr("sys.stdout", BrokenStdout())
assert detect_coding_agent() == "unknown"
def test_result_is_always_a_known_literal(clean_env):
"""PII guarantee: the return value can only ever be a known literal.
Every marker is set to a value that would be catastrophic to emit, and the
result must still come from the fixed vocabulary.
"""
sensitive = "/Users/jane.doe/secrets/api-key-sk-live-1234"
for var in ALL_MARKERS:
clean_env.setenv(var, sensitive)
result = detect_coding_agent()
assert result in KNOWN_CODING_AGENTS
assert sensitive not in result
clean_env.delenv(var, raising=False)
def test_known_agents_contains_no_pii_shaped_values():
"""Every possible emitted value is a short, opaque identifier."""
for name in KNOWN_CODING_AGENTS:
assert name.replace("_", "").isalnum(), name
assert len(name) <= 32, name
def test_coding_agent_attached_to_telemetry_resource(clean_env, monkeypatch):
"""The attribute must land on the Resource, so it reaches every span."""
import os
from unittest.mock import patch
from crewai.telemetry.telemetry import Telemetry
clean_env.setenv("CLAUDECODE", "1")
with (
patch.dict(
os.environ,
{
"CREWAI_DISABLE_TELEMETRY": "false",
"CREWAI_DISABLE_TRACKING": "false",
"OTEL_SDK_DISABLED": "false",
},
),
patch("crewai.telemetry.telemetry.TracerProvider"),
):
telemetry = Telemetry()
telemetry._initialized = False
telemetry.__init__()
assert telemetry.resource.attributes["coding_agent"] == "claude_code"
def test_coding_agent_span_emits_once(clean_env, monkeypatch):
from crewai.telemetry.telemetry import Telemetry
clean_env.setenv("CLAUDECODE", "1")
telemetry = Telemetry()
telemetry._coding_agent_reported = False
emitted: list[str] = []
monkeypatch.setattr(telemetry, "feature_usage_span", emitted.append)
telemetry.coding_agent_span()
telemetry.coding_agent_span()
telemetry.coding_agent_span()
assert emitted == ["coding_agent:claude_code"]