Skip to content

V2 OTLP exporters send percent-encoded header values unchanged #55002

Open

Description

@sdivyanshu90

Description

V2 sends percent-encoded OTEL_EXPORTER_OTLP_HEADERS values unchanged to the collector. For example, Authorization=Bearer%20test-token arrives as Bearer%20test-token, so a collector expecting Bearer test-token rejects the export.

Both the log and trace exporter share parseHeaders() in packages/util/src/observability/otlp.ts, which splits the entries but does not decode their values. The OTLP exporter specification uses the W3C Baggage header format.

Plugins

None

OpenCode version

2.0.27 (V2)

Steps to reproduce

  1. Start a local OTLP/HTTP collector that records request headers.
  2. Set OTEL_EXPORTER_OTLP_ENDPOINT to that collector and OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20test-token,x-label=one%2Ctwo.
  3. Emit a log or span and flush the exporter.

Observed: Authorization: Bearer%20test-token and x-label: one%2Ctwo.

Expected: Authorization: Bearer test-token and x-label: one,two.

This reproduces with both production exporters against a loopback collector. The existing fix for raw = characters in header values (#22201) does not address percent decoding; that behavior should remain intact.

Operating System

Linux

Terminal

bash

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this on 2.0.27 and on the latest dev (d7cfdaa).

    Steps:

    • Point OTEL_EXPORTER_OTLP_ENDPOINT at a local collector that records request headers.
    • Set OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer%20test-token,x-label=one%2Ctwo'.
    • Run opencode debug paths.

    The collector gets requests on both /v1/logs and /v1/traces, and every one has authorization: Bearer%20test-token and x-label: one%2Ctwo instead of the decoded values. As you found, parseHeaders() in packages/util/src/observability/otlp.ts splits the entries but never decodes them. resourceAttributes() in the same file does decode its values.

    The script below reproduces it. Related: #22202 / #22201, which fixed raw = in header values but not percent-decoding.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces #55002: OTLP header values are sent percent-encoded.
    # Usage: ./repro.sh            (uses `opencode` from PATH)
    #        OC_DEV=/path/to/opencode-checkout ./repro.sh   (runs from source)
    set -u
    DIR=$(mktemp -d)
    cat > "$DIR/collector.ts" <<'TS'
    Bun.serve({ port: 4318, hostname: "127.0.0.1", async fetch(req) {
      await req.arrayBuffer()
      console.log(JSON.stringify({ path: new URL(req.url).pathname, authorization: req.headers.get("authorization"), "x-label": req.headers.get("x-label") }))
      return new Response("{}", { headers: { "content-type": "application/json" } })
    }})
    TS
    bun "$DIR/collector.ts" > "$DIR/collector.log" 2>&1 &
    COLLECTOR=$!
    sleep 1
    export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
    export OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer%20test-token,x-label=one%2Ctwo'
    mkdir -p "$DIR/proj" && cd "$DIR/proj"
    if [ -n "${OC_DEV:-}" ]; then
      timeout 60 bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" "$OC_DEV/packages/cli/src/index.ts" debug paths >/dev/null 2>&1
    else
      opencode --version
      timeout 60 opencode debug paths >/dev/null 2>&1
    fi
    sleep 2
    kill $COLLECTOR
    echo "Collector received:"; cat "$DIR/collector.log"
    echo "Expected: authorization=\"Bearer test-token\", x-label=\"one,two\""

    Run it: bash repro.sh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions