From 0edd10963130a9e0780d9b5dd551a0d4590065ee Mon Sep 17 00:00:00 2001 From: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 07:22:02 +0000 Subject: [PATCH 1/5] fix(#5436): inject --header values into collector YAML config The otelcol-contrib Collector reads headers from its YAML config (exporters.otlphttp.headers), not from OTEL_EXPORTER_OTLP_HEADERS which is an OpenTelemetry SDK env var. The script was setting OTEL_EXPORTER_OTLP_HEADERS, which the Collector silently ignored, so --header flags like x-mlflow-experiment-id had no effect. Generate a runtime config file that injects parsed headers into the otlphttp exporter's headers block as YAML key-value pairs. The script still merges --header flags with any pre-existing OTEL_EXPORTER_OTLP_HEADERS env var for backwards compatibility. Header values are double-quoted for YAML safety. The temp config is cleaned up on exit via trap. Note: make lint could not run in sandbox (pre-commit failed due to network restrictions). Manual verification of YAML output confirmed correct structure for: no headers, single header, multiple headers, and values containing YAML special characters. Closes #5436 --- hack/upload-traces.sh | 69 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/hack/upload-traces.sh b/hack/upload-traces.sh index 48d65d6c3..365c4b15d 100755 --- a/hack/upload-traces.sh +++ b/hack/upload-traces.sh @@ -20,8 +20,9 @@ Arguments: --endpoint OTLP HTTP endpoint (e.g. http://localhost:4318) --header key=value Header to send with OTLP requests (repeatable) -Headers are passed via the OTEL_EXPORTER_OTLP_HEADERS env var. Any value -already set in the environment is preserved and appended to. +Headers are injected into the Collector's otlphttp exporter config. +Any key=value pairs already set in the OTEL_EXPORTER_OTLP_HEADERS env +var are also included and merged with --header flags. Examples: $(basename "$0") run/agent-run-123/run-telemetry.jsonl --endpoint http://localhost:4318 @@ -89,19 +90,65 @@ else fi echo "include: $INCLUDE" -# --- run collector --- -export REPLAY_INCLUDE="$INCLUDE" -export REPLAY_ENDPOINT="$ENDPOINT" - -# Merge --header flags with any existing OTEL_EXPORTER_OTLP_HEADERS. +# --- merge headers --- +# Combine --header flags with any pre-existing OTEL_EXPORTER_OTLP_HEADERS +# into a single comma-separated string for config generation. +MERGED_HEADERS="${OTEL_EXPORTER_OTLP_HEADERS:-}" if [[ -n "$HEADERS" ]]; then - if [[ -n "${OTEL_EXPORTER_OTLP_HEADERS:-}" ]]; then - export OTEL_EXPORTER_OTLP_HEADERS="${OTEL_EXPORTER_OTLP_HEADERS},${HEADERS}" + if [[ -n "$MERGED_HEADERS" ]]; then + MERGED_HEADERS="${MERGED_HEADERS},${HEADERS}" else - export OTEL_EXPORTER_OTLP_HEADERS="$HEADERS" + MERGED_HEADERS="$HEADERS" fi fi +# --- generate runtime config --- +# The otelcol-contrib Collector reads headers from its YAML config +# (exporters.otlphttp.headers), not from OTEL_EXPORTER_OTLP_HEADERS. +# Generate a runtime config that injects any headers into the YAML. +export REPLAY_INCLUDE="$INCLUDE" +export REPLAY_ENDPOINT="$ENDPOINT" + +RUNTIME_CONFIG=$(mktemp "${TMPDIR:-/tmp}/otelcol-config-XXXXXX.yaml") +cleanup() { rm -f "$RUNTIME_CONFIG"; } +trap cleanup EXIT + +cat > "$RUNTIME_CONFIG" <<'YAML' +receivers: + otlpjsonfile: + include: + - "${env:REPLAY_INCLUDE}" + start_at: beginning + +exporters: + otlphttp: + endpoint: "${env:REPLAY_ENDPOINT}" +YAML + +if [[ -n "$MERGED_HEADERS" ]]; then + echo " headers:" >> "$RUNTIME_CONFIG" + IFS=',' read -ra HEADER_ARRAY <<< "$MERGED_HEADERS" + for h in "${HEADER_ARRAY[@]}"; do + key="${h%%=*}" + value="${h#*=}" + # Escape double quotes for YAML safety + value="${value//\"/\\\"}" + printf ' %s: "%s"\n' "$key" "$value" >> "$RUNTIME_CONFIG" + done +fi + +cat >> "$RUNTIME_CONFIG" <<'YAML' + +service: + pipelines: + traces: + receivers: [otlpjsonfile] + exporters: [otlphttp] +YAML + echo "endpoint: $ENDPOINT" +if [[ -n "$MERGED_HEADERS" ]]; then + echo "headers: $MERGED_HEADERS" +fi echo "starting otelcol-contrib (runs continuously, watching for new data; press Ctrl+C to stop)..." -otelcol-contrib --config "$CONFIG_TEMPLATE" +otelcol-contrib --config "$RUNTIME_CONFIG" From 1722ffb24046084c13b2fcccd89f619fd05a8286 Mon Sep 17 00:00:00 2001 From: Hector Martinez Date: Wed, 22 Jul 2026 13:44:01 +0200 Subject: [PATCH 2/5] chore(traces): Add debug information to upload script Signed-off-by: Hector Martinez --- hack/upload-traces.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hack/upload-traces.sh b/hack/upload-traces.sh index 365c4b15d..91a334a9d 100755 --- a/hack/upload-traces.sh +++ b/hack/upload-traces.sh @@ -150,5 +150,13 @@ echo "endpoint: $ENDPOINT" if [[ -n "$MERGED_HEADERS" ]]; then echo "headers: $MERGED_HEADERS" fi + +echo "" +echo "Temporal configuration file: $RUNTIME_CONFIG" +echo "---" +cat "$RUNTIME_CONFIG" +echo "---" +echo "" + echo "starting otelcol-contrib (runs continuously, watching for new data; press Ctrl+C to stop)..." otelcol-contrib --config "$RUNTIME_CONFIG" From ab0ed98427a28e02c295860cc38bf2825ecf6bcf Mon Sep 17 00:00:00 2001 From: fullsend-fix <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:55:06 +0000 Subject: [PATCH 3/5] fix: remove dead CONFIG_TEMPLATE code and harden YAML header injection - Remove unused SCRIPT_DIR, CONFIG_TEMPLATE variable and its file existence check (dead code after switch to RUNTIME_CONFIG) - Validate header keys match [a-zA-Z0-9_-]+ to prevent YAML injection - Strip newline/carriage-return characters from header values - Switch to single-quoted YAML scalars (no backslash escape sequences) Addresses review feedback on #5442 --- hack/upload-traces.sh | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/hack/upload-traces.sh b/hack/upload-traces.sh index 91a334a9d..f78aaf9e7 100755 --- a/hack/upload-traces.sh +++ b/hack/upload-traces.sh @@ -1,9 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CONFIG_TEMPLATE="${SCRIPT_DIR}/upload-traces-otelcol-config.yaml" - die() { echo "error: $*" >&2; exit 1; } usage() { @@ -72,9 +69,6 @@ done command -v otelcol-contrib >/dev/null 2>&1 \ || die "otelcol-contrib not found. Install: https://github.com/open-telemetry/opentelemetry-collector-releases/releases" -[[ -f "$CONFIG_TEMPLATE" ]] \ - || die "config template not found: $CONFIG_TEMPLATE" - # --- resolve to absolute include pattern --- if [[ -d "$SOURCE" ]]; then INCLUDE="$(cd "$SOURCE" && pwd)/**/*.jsonl" @@ -131,9 +125,16 @@ if [[ -n "$MERGED_HEADERS" ]]; then for h in "${HEADER_ARRAY[@]}"; do key="${h%%=*}" value="${h#*=}" - # Escape double quotes for YAML safety - value="${value//\"/\\\"}" - printf ' %s: "%s"\n' "$key" "$value" >> "$RUNTIME_CONFIG" + # Validate key contains only safe characters + [[ "$key" =~ ^[a-zA-Z0-9_-]+$ ]] \ + || die "invalid header key (must match [a-zA-Z0-9_-]+): $key" + # Strip newlines/carriage returns to prevent YAML injection + value="${value//$'\n'/}" + value="${value//$'\r'/}" + # Use single-quoted YAML scalar (no escape sequences interpreted); + # the only special case is a literal single quote, represented as ''. + value="${value//\'/\'\'}" + printf " %s: '%s'\n" "$key" "$value" >> "$RUNTIME_CONFIG" done fi From 2843acf6847f02be386ba45e84abe14e264c57ef Mon Sep 17 00:00:00 2001 From: fullsend-fix <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:03:42 +0000 Subject: [PATCH 4/5] fix(hack): remove unused otelcol config template The committed upload-traces-otelcol-config.yaml is no longer referenced after the script was updated to generate the Collector config at runtime. Remove the dead file. Addresses review feedback on #5442 Signed-off-by: Hector Martinez --- hack/upload-traces-otelcol-config.yaml | 15 --------------- hack/upload-traces.sh | 16 ++++------------ 2 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 hack/upload-traces-otelcol-config.yaml diff --git a/hack/upload-traces-otelcol-config.yaml b/hack/upload-traces-otelcol-config.yaml deleted file mode 100644 index 601f6c366..000000000 --- a/hack/upload-traces-otelcol-config.yaml +++ /dev/null @@ -1,15 +0,0 @@ -receivers: - otlpjsonfile: - include: - - "${env:REPLAY_INCLUDE}" - start_at: beginning - -exporters: - otlphttp: - endpoint: "${env:REPLAY_ENDPOINT}" - -service: - pipelines: - traces: - receivers: [otlpjsonfile] - exporters: [otlphttp] diff --git a/hack/upload-traces.sh b/hack/upload-traces.sh index f78aaf9e7..54800129f 100755 --- a/hack/upload-traces.sh +++ b/hack/upload-traces.sh @@ -125,14 +125,11 @@ if [[ -n "$MERGED_HEADERS" ]]; then for h in "${HEADER_ARRAY[@]}"; do key="${h%%=*}" value="${h#*=}" - # Validate key contains only safe characters [[ "$key" =~ ^[a-zA-Z0-9_-]+$ ]] \ || die "invalid header key (must match [a-zA-Z0-9_-]+): $key" - # Strip newlines/carriage returns to prevent YAML injection value="${value//$'\n'/}" value="${value//$'\r'/}" - # Use single-quoted YAML scalar (no escape sequences interpreted); - # the only special case is a literal single quote, represented as ''. + value="${value//\$/\$\$}" value="${value//\'/\'\'}" printf " %s: '%s'\n" "$key" "$value" >> "$RUNTIME_CONFIG" done @@ -149,15 +146,10 @@ YAML echo "endpoint: $ENDPOINT" if [[ -n "$MERGED_HEADERS" ]]; then - echo "headers: $MERGED_HEADERS" + IFS=',' read -ra _hdr_arr <<< "$MERGED_HEADERS" + header_count=${#_hdr_arr[@]} + echo "headers: ${header_count} configured (values redacted)" fi -echo "" -echo "Temporal configuration file: $RUNTIME_CONFIG" -echo "---" -cat "$RUNTIME_CONFIG" -echo "---" -echo "" - echo "starting otelcol-contrib (runs continuously, watching for new data; press Ctrl+C to stop)..." otelcol-contrib --config "$RUNTIME_CONFIG" From b6d72c2b69d5559491dda21c12193144cc2cee4b Mon Sep 17 00:00:00 2001 From: Hector Martinez Date: Wed, 22 Jul 2026 16:58:08 +0200 Subject: [PATCH 5/5] fix(#5436): move headers to user-edited config file Replace dynamic --header flag parsing with a static config template that users edit directly. otelcol-contrib reads headers from its YAML config, not from env vars, so letting users write them in the config file avoids all the escaping, merging, and injection surface area. If yq is installed, the script prompts for confirmation when no headers are configured. Endpoint and include pattern are still passed via env vars with confmap escaping. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hector Martinez --- hack/upload-traces-otelcol-config.yaml | 21 +++++ hack/upload-traces.sh | 111 ++++++++----------------- 2 files changed, 55 insertions(+), 77 deletions(-) create mode 100644 hack/upload-traces-otelcol-config.yaml diff --git a/hack/upload-traces-otelcol-config.yaml b/hack/upload-traces-otelcol-config.yaml new file mode 100644 index 000000000..a3206e309 --- /dev/null +++ b/hack/upload-traces-otelcol-config.yaml @@ -0,0 +1,21 @@ +receivers: + otlpjsonfile: + include: + - "${env:REPLAY_INCLUDE}" + start_at: beginning + +exporters: + otlphttp: + endpoint: "${env:REPLAY_ENDPOINT}" + # Add headers below. otelcol-contrib ignores the OTEL_EXPORTER_OTLP_HEADERS + # env var; headers must be set here in the YAML config. + # + # headers: + # Authorization: "Bearer mytoken" + # x-mlflow-experiment-id: "42" + +service: + pipelines: + traces: + receivers: [otlpjsonfile] + exporters: [otlphttp] diff --git a/hack/upload-traces.sh b/hack/upload-traces.sh index 54800129f..ac7ae84b3 100755 --- a/hack/upload-traces.sh +++ b/hack/upload-traces.sh @@ -1,11 +1,14 @@ #!/usr/bin/env bash set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONFIG="${SCRIPT_DIR}/upload-traces-otelcol-config.yaml" + die() { echo "error: $*" >&2; exit 1; } usage() { cat < --endpoint [--header key=value]... +Usage: $(basename "$0") --endpoint Replay OTLP JSON traces into an OTLP HTTP endpoint. @@ -15,15 +18,19 @@ pushes them to any OTLP-speaking backend via otelcol-contrib. Arguments: A .jsonl file, directory, or glob pattern --endpoint OTLP HTTP endpoint (e.g. http://localhost:4318) - --header key=value Header to send with OTLP requests (repeatable) -Headers are injected into the Collector's otlphttp exporter config. -Any key=value pairs already set in the OTEL_EXPORTER_OTLP_HEADERS env -var are also included and merged with --header flags. +To send headers (auth tokens, experiment IDs, etc.), edit the config file +directly — otelcol-contrib reads headers from its YAML config, not from +env vars or CLI flags: + + $CONFIG + +If yq is installed and no headers are configured, the script prompts for +confirmation before sending traces without authentication. Examples: $(basename "$0") run/agent-run-123/run-telemetry.jsonl --endpoint http://localhost:4318 - $(basename "$0") run/ --endpoint http://localhost:4318 --header x-mlflow-experiment-id=42 + $(basename "$0") run/ --endpoint http://localhost:4318 $(basename "$0") 'run/*/run-telemetry.jsonl' --endpoint http://localhost:4318 The collector runs continuously and watches for new data. This is useful @@ -40,18 +47,12 @@ EOF # --- parse args --- SOURCE="" ENDPOINT="" -HEADERS="" while [[ $# -gt 0 ]]; do case "$1" in --endpoint) [[ $# -ge 2 ]] || die "--endpoint requires a value" ENDPOINT="$2"; shift 2 ;; - --header) - [[ $# -ge 2 ]] || die "--header requires a key=value" - [[ "$2" == *=* ]] || die "--header value must be key=value, got: $2" - if [[ -n "$HEADERS" ]]; then HEADERS="${HEADERS},$2"; else HEADERS="$2"; fi - shift 2 ;; --help|-h) usage ;; -*) @@ -69,13 +70,15 @@ done command -v otelcol-contrib >/dev/null 2>&1 \ || die "otelcol-contrib not found. Install: https://github.com/open-telemetry/opentelemetry-collector-releases/releases" +[[ -f "$CONFIG" ]] \ + || die "config not found: $CONFIG" + # --- resolve to absolute include pattern --- if [[ -d "$SOURCE" ]]; then INCLUDE="$(cd "$SOURCE" && pwd)/**/*.jsonl" elif [[ -f "$SOURCE" ]]; then INCLUDE="$(cd "$(dirname "$SOURCE")" && pwd)/$(basename "$SOURCE")" else - # Treat as a glob pattern — make it absolute relative to cwd if [[ "$SOURCE" == /* ]]; then INCLUDE="$SOURCE" else @@ -84,72 +87,26 @@ else fi echo "include: $INCLUDE" -# --- merge headers --- -# Combine --header flags with any pre-existing OTEL_EXPORTER_OTLP_HEADERS -# into a single comma-separated string for config generation. -MERGED_HEADERS="${OTEL_EXPORTER_OTLP_HEADERS:-}" -if [[ -n "$HEADERS" ]]; then - if [[ -n "$MERGED_HEADERS" ]]; then - MERGED_HEADERS="${MERGED_HEADERS},${HEADERS}" - else - MERGED_HEADERS="$HEADERS" +# --- run collector --- +# confmap recursively expands ${scheme:...} tokens in resolved values. +# Escape literal $ as $$ so paths/URLs can't pivot into env var lookups. +escape_confmap() { printf '%s' "${1//\$/\$\$}"; } + +REPLAY_INCLUDE="$(escape_confmap "$INCLUDE")" +export REPLAY_INCLUDE +REPLAY_ENDPOINT="$(escape_confmap "$ENDPOINT")" +export REPLAY_ENDPOINT + +if command -v yq >/dev/null 2>&1; then + header_count="$(yq '.exporters.otlphttp.headers | length' "$CONFIG" 2>/dev/null || echo 0)" + if [[ "$header_count" -eq 0 ]]; then + echo "warning: no headers configured in $CONFIG" + echo "traces will be sent without authentication headers." + read -rp "Continue? [y/N] " answer + [[ "$answer" =~ ^[Yy]$ ]] || exit 0 fi fi -# --- generate runtime config --- -# The otelcol-contrib Collector reads headers from its YAML config -# (exporters.otlphttp.headers), not from OTEL_EXPORTER_OTLP_HEADERS. -# Generate a runtime config that injects any headers into the YAML. -export REPLAY_INCLUDE="$INCLUDE" -export REPLAY_ENDPOINT="$ENDPOINT" - -RUNTIME_CONFIG=$(mktemp "${TMPDIR:-/tmp}/otelcol-config-XXXXXX.yaml") -cleanup() { rm -f "$RUNTIME_CONFIG"; } -trap cleanup EXIT - -cat > "$RUNTIME_CONFIG" <<'YAML' -receivers: - otlpjsonfile: - include: - - "${env:REPLAY_INCLUDE}" - start_at: beginning - -exporters: - otlphttp: - endpoint: "${env:REPLAY_ENDPOINT}" -YAML - -if [[ -n "$MERGED_HEADERS" ]]; then - echo " headers:" >> "$RUNTIME_CONFIG" - IFS=',' read -ra HEADER_ARRAY <<< "$MERGED_HEADERS" - for h in "${HEADER_ARRAY[@]}"; do - key="${h%%=*}" - value="${h#*=}" - [[ "$key" =~ ^[a-zA-Z0-9_-]+$ ]] \ - || die "invalid header key (must match [a-zA-Z0-9_-]+): $key" - value="${value//$'\n'/}" - value="${value//$'\r'/}" - value="${value//\$/\$\$}" - value="${value//\'/\'\'}" - printf " %s: '%s'\n" "$key" "$value" >> "$RUNTIME_CONFIG" - done -fi - -cat >> "$RUNTIME_CONFIG" <<'YAML' - -service: - pipelines: - traces: - receivers: [otlpjsonfile] - exporters: [otlphttp] -YAML - echo "endpoint: $ENDPOINT" -if [[ -n "$MERGED_HEADERS" ]]; then - IFS=',' read -ra _hdr_arr <<< "$MERGED_HEADERS" - header_count=${#_hdr_arr[@]} - echo "headers: ${header_count} configured (values redacted)" -fi - echo "starting otelcol-contrib (runs continuously, watching for new data; press Ctrl+C to stop)..." -otelcol-contrib --config "$RUNTIME_CONFIG" +otelcol-contrib --config "$CONFIG"