{"record":{"id":"06fe750ce0e964dd","repo":"jaegertracing/jaeger","slug":"span-id-must-not-be-all-zero","errorCode":null,"errorMessage":"span ID must not be all zero","messagePattern":"span ID must not be all zero","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/jaeger/internal/extension/jaegerquery/internal/mcptools/internal/handlers/get_span_details.go","lineNumber":305,"sourceCode":"\n// parseSpanID parses a span ID string into a pcommon.SpanID.\nfunc parseSpanID(spanIDStr string) (pcommon.SpanID, error) {\n\t// Parse hex string - SpanID is 8 bytes (16 hex characters)\n\tif len(spanIDStr) != 16 {\n\t\treturn pcommon.SpanID{}, fmt.Errorf(\"span ID must be 16 hex characters, got %d\", len(spanIDStr))\n\t}\n\n\tvar spanID pcommon.SpanID\n\tbytes, err := hex.DecodeString(spanIDStr)\n\tif err != nil {\n\t\treturn pcommon.SpanID{}, fmt.Errorf(\"invalid hex string: %w\", err)\n\t}\n\n\tcopy(spanID[:], bytes)\n\t// The all-zero span ID can never identify a real span: SpanID.String()\n\t// returns \"\" for it, so it would silently never match in the lookup.\n\tif spanID.IsEmpty() {\n\t\treturn pcommon.SpanID{}, errors.New(\"span ID must not be all zero\")\n\t}\n\treturn spanID, nil\n}\n","sourceCodeStart":287,"sourceCodeEnd":309,"githubUrl":"https://github.com/jaegertracing/jaeger/blob/806f4447841ecdb60519f408b004a599d515f437/cmd/jaeger/internal/extension/jaegerquery/internal/mcptools/internal/handlers/get_span_details.go#L287-L309","documentation":"parseSpanID decodes a hex span ID into pcommon.SpanID and rejects the all-zero value, because SpanID.String() renders it as \"\" and it would silently never match any real span during the filtered lookup. This guard turns a silent no-match into an explicit input error.","triggerScenarios":"Passing span_ids entries of \"0000000000000000\" (or all-zero after hex decoding) to get_span_details, so parseSpanID returns this error during buildQuery.","commonSituations":"Placeholder/nil span IDs from unmarshaled protobuf or zero-valued structs; a default-initialized SpanID stringified and fed back in; copy-paste of an all-zero sentinel from logs.","solutions":["Replace the zero span ID with the real span ID from the trace's span metadata","Filter out all-zero span IDs client-side before calling the tool","Fix the upstream producer that emitted a zero SpanID (usually an unset field in an exported trace)"],"exampleFix":"// before\nids := []string{span.ID().String()} // may be \"0000000000000000\" if span unset\n// after\nvar filtered []string\nfor _, id := range candidateIDs {\n    if id != \"0000000000000000\" && strings.Trim(id, \"0\") != \"\" {\n        filtered = append(filtered, id)\n    }\n}\nout, err := h.Handle(ctx, req, types.GetSpanDetailsInput{TraceID: tid, SpanIDs: filtered})","handlingStrategy":"validation","validationCode":"func isZeroSpanID(id string) bool {\n    b, err := hex.DecodeString(id)\n    return err != nil || !bytes.Contains(b, []byte{1}) // all zeros or undecodable\n}\nids := slices.DeleteFunc(rawIDs, isZeroSpanID)","typeGuard":null,"tryCatchPattern":"out, err := handler.Handle(ctx, req, input)\nif err != nil && strings.Contains(err.Error(), \"span ID must not be all zero\") {\n    return fmt.Errorf(\"input contained an unset/zero span ID: %v\", input.SpanIDs)\n}","preventionTips":["Filter zero-value span IDs produced by unset protobuf fields","Never stringify a default-initialized pcommon.SpanID as a real ID","Investigate producers that emit zero SpanIDs in exported traces"],"tags":["jaeger","mcp","validation","span-id"],"backgroundTag":"invalid-identifier-value","analyzedSha":"806f4447841ecdb60519f408b004a599d515f437","analyzedAt":"2026-09-01T02:39:22.140Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}