dagger/dagger · error

failed to json unmarshal: %w

Error message

failed to json unmarshal: %w

What it means

UnmarshalProtoJSONs in engine/clientdb/span.go decodes a JSON array of protobuf messages (span attributes, log payloads) stored in the client-side trace database. Before it can protojson-decode each element, the outer payload must itself be a valid JSON array of raw JSON values. This error wraps the encoding/json failure when the stored bytes are not parseable JSON at all, so the function aborts before touching any protobuf message.

Source

Thrown at engine/clientdb/span.go:103

}

// StartTime returns the start time of the span
func (ros *readOnlySpan) StartTime() time.Time {
	return time.Unix(0, ros.DB.StartTime)
}

// EndTime returns the end time of the span
func (ros *readOnlySpan) EndTime() time.Time {
	if ros.DB.EndTime.Valid {
		return time.Unix(0, ros.DB.EndTime.Int64)
	}
	return time.Time{}
}

func UnmarshalProtoJSONs[T proto.Message](pb []byte, base T, out *[]T) error {
	var msgs []json.RawMessage
	if err := json.Unmarshal(pb, &msgs); err != nil {
		return fmt.Errorf("failed to json unmarshal: %w", err)
	}
	protos := make([]T, len(msgs))
	for i, msg := range msgs {
		pl := proto.Clone(base).(T)
		if err := protojson.Unmarshal(msg, pl); err != nil {
			return fmt.Errorf("failed to protojson unmarshal %s into %T: %w", msg, pl, err)
		}
		protos[i] = pl
	}
	*out = protos
	return nil
}

func MarshalProtoJSONs[T proto.Message](protos []T) ([]byte, error) {
	msgs := make([]json.RawMessage, len(protos))
	for i, msg := range protos {
		pl, err := protojson.Marshal(msg)
		if err != nil {

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Log the raw pb bytes (hex/utf-8 preview) and run them through json.Valid to confirm the payload is actually JSON.
  2. Verify the bytes were written by MarshalProtoJSONs; if the source stores proto binary, switch the writer to MarshalProtoJSONs or decode with proto.Unmarshal instead.
  3. Check for truncation: compare stored blob length to what the writer produced; re-export/re-ingest the affected spans.
  4. Upgrade/migrate old rows written by a previous schema version to the current JSON-array format.

Example fix

// before
err := UnmarshalProtoJSONs(rawBlob, baseProto, &out) // rawBlob is proto binary
// after
jsonBytes, err := MarshalProtoJSONs(msgs) // persist JSON-array form
if err != nil { return err }
err = UnmarshalProtoJSONs(jsonBytes, baseProto, &out)
Defensive patterns

Strategy: validation

Validate before calling

if len(pb) == 0 || !json.Valid(pb) {
    return fmt.Errorf("payload is not valid JSON (%d bytes)", len(pb))
}
var probe []json.RawMessage
if err := json.Unmarshal(pb, &probe); err != nil {
    return fmt.Errorf("payload is not a JSON array: %w", err)
}

Try / catch

var out []pb.Span
if err := UnmarshalProtoJSONs(raw, base, &out); err != nil {
    log.Warn("unparseable span payload, skipping", "err", err, "bytes", len(raw))
    return nil // degrade gracefully instead of failing the query
}

Prevention

When it happens

Trigger: Calling UnmarshalProtoJSONs (directly or via LogsToPB, Attributes, captureLogLines, classifyLogSpan, beneathInternal, serviceInstallSpan) with pb bytes that are not valid JSON — e.g. empty slice, truncated data, a plain proto binary encoding instead of JSON, or hand-written rows in the DB that were never produced by MarshalProtoJSONs.

Common situations: Reading a span row written by an older schema or a different writer; a DB blob corrupted or truncated on disk; passing the proto binary wire format where the JSON-array format is expected; copying/migrating rows between databases with encoding loss (e.g. stored as TEXT and mangled).

Related errors


AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05). Data as JSON: /api/errors/7870bdec8977fc14. Report an issue: GitHub.