Skip to content

fix: honor gen_ai payload capture disable regardless of OTel env vars - #18

Merged
shreyas-n-harness merged 1 commit into
harness:mainfrom
shreyas70:fix/genai-payload-capture-disabled
Jul 27, 2026
Merged

fix: honor gen_ai payload capture disable regardless of OTel env vars#18
shreyas-n-harness merged 1 commit into
harness:mainfrom
shreyas70:fix/genai-payload-capture-disabled

Conversation

@shreyas70

Copy link
Copy Markdown
Contributor

Problem

gen_ai_input.message keeps showing up in UDP for services that set GenAI payload capture to false (HARNESS_GEN_AI_PAYLOAD_CAPTURE_ENABLED=false / gen_ai.payload_capture_enabled: false).

The flag only ever worked in the enable direction. Three leaks:

  1. genai_env.maybe_set_genai_payload_capture_env_vars() checked pre-existing OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT / OTEL_SEMCONV_STABILITY_OPT_IN before reading config and bailed out. On disable it also just returned without forcing anything off. Any deployment that sets those OTel vars itself (shared base image, another auto-instrumentation layer) keeps capturing content, so OpenAI / Anthropic / Google GenAI wrappers emit gen_ai.input.messages.
  2. The LiteLLM wrapper set gen_ai.input.messages gated only on litellm.turn_off_message_logging / otel_logger.message_logging — it never consulted Harness config.
  3. The MCP mirror had the same "already set, skip" hole for TRACELOOP_TRACE_CONTENT.

Fix

Payload capture is a privacy control, so false must mean false: config now wins over the environment in the disable direction, while the enable path keeps supplying defaults only (user-set env still wins).

  • genai_env.py — config checked first; disabling forces OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=NO_CONTENT and patches the cached semconv stability mode for GEN_AI to DEFAULT, overwriting whatever the environment had.
  • litellm/__init__.py — explicit payload_capture_enabled gate before setting gen_ai.input.messages.
  • mcp/gen_ai_mirror.py — force TRACELOOP_TRACE_CONTENT=false when disabled, even if preset.
  • New GenAiPayloadScrubSpanProcessor, wired as the outermost layer of the default pipeline. When capture is disabled it strips gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instruction, gen_ai.prompt*, gen_ai.completion*, traceloop.entity.input/output at on_end. This is the backstop for third-party OTel contrib instrumentations the SDK does not wrap. Cheap no-op when capture is enabled.

Test plan

  • ./scripts/run-unit-tests.sh — 219 passed, 1 skipped. The 2 gRPC failures (test_grpc_1, test_grpc_2) reproduce on unmodified main in the same sandbox (local port binding), unrelated to this change.
  • New coverage: capture disabled with OTel vars preset forces NO_CONTENT; LiteLLM emits no input messages when disabled; MCP forces TRACELOOP_TRACE_CONTENT=false; enabled path leaves user env untouched; scrub processor strips attributes when disabled and no-ops when enabled.
  • Verify on a service with HARNESS_GEN_AI_PAYLOAD_CAPTURE_ENABLED=false that gen_ai_input.message no longer reaches UDP.

Note

test/conftest.py's reset_singletons does not reset the module-level _applied flag in genai_env.py, so in a full run that function's logic executes only once. The new tests reset it locally. Worth a separate cleanup.

Made with Cursor

Payload capture only worked in the enable direction: genai_env checked
pre-existing OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT /
OTEL_SEMCONV_STABILITY_OPT_IN before reading config and returned without
forcing anything off, the LiteLLM wrapper gated gen_ai.input.messages only
on litellm's own flags, and the MCP mirror skipped TRACELOOP_TRACE_CONTENT
when preset. Deployments that set those OTel vars themselves kept shipping
prompts even with payload_capture_enabled=false.

Disable is a privacy control, so config now wins over the environment: it
forces NO_CONTENT and a non-experimental semconv mode, gates the LiteLLM
input attribute on config, and force-disables Traceloop content capture.
A scrub span processor in the default pipeline strips payload attributes
before export as a backstop for instrumentations the SDK does not wrap.

Co-authored-by: Cursor <cursoragent@cursor.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@shreyas-n-harness
shreyas-n-harness merged commit 9e7485b into harness:main Jul 27, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants