#6786·quickwit

OTLP record with `time_unix_nano: 0` is rejected with an empty `error_message`, and the response is JSON for a protobuf request

Author: DeviousCardiCreated Sep 8, 2026Updated Sep 8, 2026

Repository: quickwit-oss/quickwit Version: 0.8.2 (quickwit/quickwit:0.8.2), QW_ENABLE_OTLP_ENDPOINT=true Reproduced first-hand: yes, on 2026-09-09.

What happens

A log record whose time_unix_nano is 0 is answered HTTP 200 with

json
{ "partial_success": { "rejected_log_records": 1, "error_message": "" } }

and never becomes queryable. Two things about that response are worth separating.

The rejection is reported, but with no reason. rejected_log_records: 1 is the right channel and Quickwit uses it, which several stores do not. But error_message is empty, so a caller learns that one record was dropped and nothing about why. observed_time_unix_nano is present and carries a real instant, so there is somewhere to file the record if Quickwit wanted one.

The response is JSON although the request was protobuf. The OTLP/HTTP specification requires the response to use the same encoding as the request. Here the body is JSON and content-type says application/json, so a conformant protobuf client cannot decode it — meaning the report above does not reach the client it is meant for. (OpenObserve makes the same mistake in the other direction, answering a JSON export with protobuf; that is filed with them separately. It is mentioned only so this is not read as singling one project out.)

Why the record is conformant

opentelemetry-proto documents time_unix_nano as the time the event occurred, with a value of 0 indicating that the timestamp is unknown or missing. It is not a malformed field — it is the defined way to say "I do not know when this happened", and an exporter with no event time emits exactly this.

Refusing it is a defensible choice. Refusing it without saying why is the part worth fixing, and four other stores accept the record.

Reproduction

bash
docker run -d --name qw -p 7280:7280 \
  -e QW_ENABLE_OTLP_ENDPOINT=true quickwit/quickwit:0.8.2 run
until curl -sf localhost:7280/health/livez >/dev/null; do sleep 1; done; sleep 10

# One record: severityText INFO, a body, attribute specmatrix.run=issue-tzero,
# observed_time_unix_nano set, and time_unix_nano = 0.
base64 -d > /tmp/tzero.pb <<'B64'
CoEBCh4KHAoMc2VydmljZS5uYW1lEgwKCnNwZWNtYXRyaXgSXwoMCgpzcGVjbWF0cml4Ek8QCRoESU5GTyobChlzcGVjbWF0cml4IHplcm8gdGltZXN0YW1wMh8KDnNwZWNtYXRyaXgucnVuEg0KC2lzc3VlLXR6ZXJvWQB2wF/KcdMY
B64

curl -s -D - -X POST localhost:7280/api/v1/otlp/v1/logs \
  -H 'Content-Type: application/x-protobuf' --data-binary @/tmp/tzero.pb
sleep 10
curl -s 'localhost:7280/api/v1/otel-logs-v0_7/search?query=attributes.specmatrix.run:issue-tzero'

Observed: HTTP 200, content-type: application/json, the partial_success body quoted above, and num_hits: 0.

Where this came from

SpecMatrix, a conformance corpus for observability backends. The check is cases/otlp-logs/timestamp-zero.yaml, which records rather than judges what a store does with an unknown timestamp — Parseable files it at the epoch, three others substitute their ingest time. It became a finding here because the record is discarded and the reason is not given.


Found by SpecMatrix, a conformance corpus for observability backends. Happy to be told this is configuration or already known — the check will record whichever it turns out to be.