mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-08-10 08:21:54 +00:00
fix(telemetry): detect runtime markers by presence, split paas from serverless
Three findings from the CodeRabbit, code-quality and Cursor reviews. The runtime loop tested truthiness while constants.py documented presence, so a platform exporting a bare CI= fell through to the TTY fallback and was mislabelled as an ordinary local run. Presence is now what it says. The assistant markers keep truthiness deliberately: there an empty value means the tool set a placeholder rather than claiming the session. DYNO and WEBSITE_INSTANCE_ID marked Heroku dynos and Azure App Service instances as serverless, and since serverless is checked first they could never reach the container label. They move to a paas context, which is what they are: long-lived containers rather than per-invocation functions. AWS_EXECUTION_ENV is dropped entirely - it is set on ECS and EC2 as well as Lambda, and AWS_LAMBDA_FUNCTION_NAME already covers Lambda without the collision. The container probe no longer wraps os.path.exists in a try/except. os.path.exists handles OSError internally and returns False, so the handler guarded a condition that cannot occur and only hid the intent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,19 +105,19 @@ def detect_runtime_context() -> str:
|
||||
"unknown" when that check cannot be made. The result is always a member
|
||||
of KNOWN_RUNTIME_CONTEXTS.
|
||||
"""
|
||||
# Presence, not truthiness: a platform that exports an empty CI= is still
|
||||
# CI, unlike the assistant markers where an empty value means the tool set
|
||||
# a placeholder rather than claiming the session.
|
||||
for context_name, env_vars in RUNTIME_CONTEXT_ENV_MARKERS:
|
||||
if any(os.environ.get(env_var) for env_var in env_vars):
|
||||
if any(env_var in os.environ for env_var in env_vars):
|
||||
return context_name
|
||||
|
||||
for env_var, expected, context_name in _EDITOR_TERM_MARKERS:
|
||||
if os.environ.get(env_var) == expected:
|
||||
return context_name
|
||||
|
||||
try:
|
||||
if os.path.exists(_DOCKER_ENV_PATH):
|
||||
return "container"
|
||||
except OSError:
|
||||
pass
|
||||
if os.path.exists(_DOCKER_ENV_PATH):
|
||||
return "container"
|
||||
|
||||
try:
|
||||
return "interactive" if sys.stdout.isatty() else "non_interactive"
|
||||
|
||||
@@ -25,6 +25,7 @@ __all__ = [
|
||||
"MAX_FILE_NAME_LENGTH",
|
||||
"NOTEBOOK_ENV_VARS",
|
||||
"NOT_SPECIFIED",
|
||||
"PAAS_ENV_VARS",
|
||||
"RUNTIME_CONTEXT_ENV_MARKERS",
|
||||
"SERVERLESS_ENV_VARS",
|
||||
"TRAINED_AGENTS_DATA_FILE",
|
||||
@@ -98,12 +99,17 @@ CI_ENV_VARS: Final[tuple[str, ...]] = (
|
||||
"TRAVIS",
|
||||
)
|
||||
SERVERLESS_ENV_VARS: Final[tuple[str, ...]] = (
|
||||
"AWS_EXECUTION_ENV",
|
||||
"AWS_LAMBDA_FUNCTION_NAME",
|
||||
"DYNO",
|
||||
"FUNCTION_TARGET",
|
||||
"K_SERVICE",
|
||||
"VERCEL",
|
||||
)
|
||||
# Managed application platforms, kept apart from serverless: their markers are
|
||||
# set for long-lived containers rather than per-invocation functions, and
|
||||
# checking them under "serverless" would have claimed every Heroku dyno and
|
||||
# Azure App Service instance before the container check could see them.
|
||||
PAAS_ENV_VARS: Final[tuple[str, ...]] = (
|
||||
"DYNO",
|
||||
"WEBSITE_INSTANCE_ID",
|
||||
)
|
||||
HOSTED_IDE_ENV_VARS: Final[tuple[str, ...]] = (
|
||||
@@ -123,6 +129,7 @@ CONTAINER_ENV_VARS: Final[tuple[str, ...]] = ("KUBERNETES_SERVICE_HOST",)
|
||||
RUNTIME_CONTEXT_ENV_MARKERS: Final[tuple[tuple[str, tuple[str, ...]], ...]] = (
|
||||
("ci", CI_ENV_VARS),
|
||||
("serverless", SERVERLESS_ENV_VARS),
|
||||
("paas", PAAS_ENV_VARS),
|
||||
("hosted_ide", HOSTED_IDE_ENV_VARS),
|
||||
("notebook", NOTEBOOK_ENV_VARS),
|
||||
("container", CONTAINER_ENV_VARS),
|
||||
|
||||
@@ -577,3 +577,36 @@ def test_all_common_attributes_land_on_exported_spans(clean_env, monkeypatch):
|
||||
assert exported["coding_agent"] == "claude_code"
|
||||
assert exported["runtime_context"] == "ci"
|
||||
assert exported["project_id"] == "proj-123"
|
||||
|
||||
|
||||
def test_runtime_markers_are_detected_by_presence(clean_env, monkeypatch):
|
||||
"""An empty value still means the platform set the marker.
|
||||
|
||||
Some platforms export a bare `CI=`; truthiness checks would drop those
|
||||
runs to the TTY fallback and mislabel them as ordinary local executions.
|
||||
"""
|
||||
monkeypatch.setattr("os.path.exists", lambda path: False)
|
||||
clean_env.setenv("CI", "")
|
||||
|
||||
assert detect_runtime_context() == "ci"
|
||||
|
||||
|
||||
def test_managed_platforms_are_not_reported_as_serverless(clean_env):
|
||||
"""Long-lived managed platforms must not claim the serverless label.
|
||||
|
||||
DYNO and WEBSITE_INSTANCE_ID mark Heroku dynos and Azure App Service
|
||||
instances, which are containers rather than per-invocation functions.
|
||||
"""
|
||||
clean_env.setenv("DYNO", "web.1")
|
||||
assert detect_runtime_context() == "paas"
|
||||
|
||||
clean_env.delenv("DYNO")
|
||||
clean_env.setenv("WEBSITE_INSTANCE_ID", "abc123")
|
||||
assert detect_runtime_context() == "paas"
|
||||
|
||||
|
||||
def test_serverless_markers_still_win_over_paas(clean_env):
|
||||
clean_env.setenv("DYNO", "web.1")
|
||||
clean_env.setenv("AWS_LAMBDA_FUNCTION_NAME", "my-fn")
|
||||
|
||||
assert detect_runtime_context() == "serverless"
|
||||
|
||||
Reference in New Issue
Block a user