{"record":{"id":"4329bfda41e7e94e","repo":"Unity-Technologies/ml-agents","slug":"getcompressedobservation-returned-null-data-for","errorCode":null,"errorMessage":"GetCompressedObservation() returned null data for sensor named {sensor.GetName()}. You must return a byte[]. If you don't want to use compressed observations, return CompressionSpec.Default() from GetCompressionSpec().","messagePattern":"GetCompressedObservation\\(\\) returned null data for sensor named (.+?)\\. You must return a byte\\[\\]\\. If you don't want to use compressed observations, return CompressionSpec\\.Default\\(\\) from GetCompressionSpec\\(\\)\\.","errorType":"exception","errorClass":"UnityAgentsException","httpStatus":null,"severity":"error","filePath":"com.unity.ml-agents/Runtime/Communicator/GrpcExtensions.cs","lineNumber":422,"sourceCode":"                {\n                    floatDataProto.Data.Add(0.0f);\n                }\n\n                observationWriter.SetTarget(floatDataProto.Data, sensor.GetObservationSpec(), 0);\n                sensor.Write(observationWriter);\n\n                observationProto = new ObservationProto\n                {\n                    FloatData = floatDataProto,\n                    CompressionType = (CompressionTypeProto)SensorCompressionType.None,\n                };\n            }\n            else\n            {\n                var compressedObs = sensor.GetCompressedObservation();\n                if (compressedObs == null)\n                {\n                    throw new UnityAgentsException(\n                        $\"GetCompressedObservation() returned null data for sensor named {sensor.GetName()}. \" +\n                        \"You must return a byte[]. If you don't want to use compressed observations, \" +\n                        \"return CompressionSpec.Default() from GetCompressionSpec().\"\n                    );\n                }\n                observationProto = new ObservationProto\n                {\n                    CompressedData = ByteString.CopyFrom(compressedObs),\n                    CompressionType = (CompressionTypeProto)sensor.GetCompressionSpec().SensorCompressionType,\n                };\n                if (compressionSpec.CompressedChannelMapping != null)\n                {\n                    observationProto.CompressedChannelMapping.AddRange(compressionSpec.CompressedChannelMapping);\n                }\n            }\n\n            // Add the dimension properties to the observationProto\n            var dimensionProperties = obsSpec.DimensionProperties;","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/Unity-Technologies/ml-agents/blob/3ecb446f75d1e7400eb404c562dc005d3164cffc/com.unity.ml-agents/Runtime/Communicator/GrpcExtensions.cs#L404-L440","documentation":"ML-Agents throws this when a sensor's GetCompressedObservation() returns null even though the sensor declared a non-default compression spec. The library promised compressed bytes over the gRPC channel but received nothing, so the observation proto cannot be built. It is a sensor implementation contract violation, not a runtime/environment failure.","triggerScenarios":"A custom SensorImplementation overrides GetCompressionSpec() to return e.g. PNG or MotionJpeg compression but its GetCompressedObservation() returns null (unimplemented, returns null on a stub, or compression failed silently). Only reached in GetObservationProto when compression is actually requested.","commonSituations":"Copy-pasting a custom sensor template without filling in GetCompressedObservation; a sensor whose underlying texture/camera capture fails and returns null; sensors that were built for uncompressed mode later switched to compressed training.","solutions":["Implement GetCompressedObservation() so it always returns a valid byte[] when a non-default CompressionSpec is declared","Return CompressionSpec.Default() from GetCompressionSpec() if you don't need compression, so the uncompressed path is used","Guard the compression path: if the compressed payload cannot be produced, fall back to uncompressed instead of returning null"],"exampleFix":"// before\npublic CompressionSpec GetCompressionSpec() => CompressionSpec.PNG();\npublic byte[] GetCompressedObservation() { return null; } // TODO\n// after\npublic CompressionSpec GetCompressionSpec() => CompressionSpec.PNG();\npublic byte[] GetCompressedObservation() => EncodePng(m_Texture); // must never return null","handlingStrategy":"validation","validationCode":"var spec = sensor.GetCompressionSpec();\nif (spec != null && !spec.Equals(CompressionSpec.Default()))\n{\n    var bytes = sensor.GetCompressedObservation();\n    if (bytes == null) throw new InvalidOperationException($\"Sensor {sensor.GetName()} must return byte[] or use CompressionSpec.Default()\");\n}","typeGuard":"bool HasValidCompressedObservation(ISensor sensor) =>\n    sensor.GetCompressionSpec() == null ||\n    sensor.GetCompressionSpec().Equals(CompressionSpec.Default()) ||\n    sensor.GetCompressedObservation() != null;","tryCatchPattern":"try { var obs = GrpcExtensions.GetObservationProto(sensor, ...); }\ncatch (UnityAgentsException e) when (e.Message.Contains(\"returned null data for sensor\"))\n{ Debug.LogError($\"Fix sensor {sensor.GetName()}: implement GetCompressedObservation or return CompressionSpec.Default()\"); }","preventionTips":["Never return null from GetCompressedObservation in custom sensors","Only declare a non-default CompressionSpec when compression is actually implemented","Unit-test custom sensors: assert GetCompressedObservation() != null when compression is declared"],"tags":["unity","ml-agents","sensor","compression","null-reference"],"backgroundTag":"null-compressed-observation","analyzedSha":"3ecb446f75d1e7400eb404c562dc005d3164cffc","analyzedAt":"2026-09-02T16:33:12.832Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T21:17:11.164Z"}