{"record":{"id":"f407fa59752650cd","repo":"headroomlabs-ai/headroom","slug":"remote-kompress-response-field-compressed-must-b","errorCode":null,"errorMessage":"remote Kompress response field 'compressed' must be a string","messagePattern":"remote Kompress response field 'compressed' must be a string","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"headroom/transforms/kompress_remote.py","lineNumber":197,"sourceCode":"        target_ratio: float | None = None,\n        *,\n        allow_download: bool = True,\n    ) -> KompressResult:\n        n_words = len(content.split())\n        if n_words < _MIN_WORDS:\n            return self._passthrough(content, n_words)\n\n        try:\n            resp = self._client.post(\n                self._url,\n                headers=self._headers,\n                json={\"content\": content, \"target_ratio\": target_ratio},\n            )\n            resp.raise_for_status()\n            data = resp.json()\n            compressed = data[\"compressed\"]\n            if not isinstance(compressed, str):\n                raise TypeError(\"remote Kompress response field 'compressed' must be a string\")\n            # Coerce the numeric/string metadata fields inside the fail-open guard.\n            # A 200 response with a malformed field (e.g. a non-numeric string, or\n            # an explicit JSON null: data.get returns None for a present key, and\n            # float(None)/int(None) raise) would otherwise escape uncaught and break\n            # the proxy request, defeating the fail-open contract this class promises.\n            result = KompressResult(\n                compressed=compressed,\n                original=content,\n                original_tokens=int(data.get(\"original_tokens\", n_words)),\n                compressed_tokens=int(data.get(\"compressed_tokens\", len(compressed.split()))),\n                compression_ratio=float(data.get(\"compression_ratio\", 1.0)),\n                model_used=str(data.get(\"model_used\", self.config.model_id)),\n            )\n        except Exception as e:  # fail OPEN — never break the proxy on a bad endpoint\n            logger.warning(\"Remote Kompress failed (%s); passing through\", e)\n            return self._passthrough(content, n_words)\n\n        # CCR stays PROXY-LOCAL: endpoint is stateless (enable_ccr=False), so we","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/transforms/kompress_remote.py#L179-L215","documentation":"Raised by the remote Kompress client when the HTTP response is a 2xx but its JSON `compressed` field is not a string (e.g. null, a number, or a nested object). The client explicitly type-checks the one field it cannot infer, because the rest of `KompressResult` falls back to word counts. It signals a server-side contract violation or a wrong URL serving a different JSON shape, not a client bug.","triggerScenarios":"POSTing `{\"content\": ..., \"target_ratio\": ...}` to `self._url` and receiving 200 with `{\"compressed\": null}`, `{\"compressed\": 123}`, a truncated proxy error page parsed as JSON with a non-string field, or pointing the client at an endpoint that returns a different schema.","commonSituations":"Version skew between the remote Kompress server and this client (server renamed the field or returns a structured result), a load balancer/gateway returning a JSON status object, or a typo'd `url` in the remote config that happens to hit another JSON API.","solutions":["Inspect the raw response: curl the endpoint with a sample payload and check `data[\"compressed\"]` is a plain JSON string","Align versions: deploy the remote Kompress server version whose response schema includes a string `compressed` field, or update the client config/URL to the correct endpoint","If an intermediary (proxy/gateway) is rewriting responses, bypass or fix it so the origin JSON reaches the client intact"],"exampleFix":"# before\nclient = KompressRemote(config)  # url points at /v1/summarize\n\n# after\nclient = KompressRemote(config.model_copy(update={\"url\": \"https://host/v1/kompress/compress\"}))\n# verify: curl -s $URL -d '{\"content\":\"hi\",\"target_ratio\":0.5}' | jq -r '.compressed | type'  # must print \"string\"","handlingStrategy":"try-catch","validationCode":"resp = client._client.post(client._url, json={\"content\": sample, \"target_ratio\": 0.5})\ndata = resp.json()\nassert isinstance(data.get(\"compressed\"), str), f\"contract drift: {type(data.get('compressed'))}\"","typeGuard":"def is_valid_kompress_response(data: dict) -> bool:\n    return isinstance(data, dict) and isinstance(data.get(\"compressed\"), str)","tryCatchPattern":"try:\n    result = remote.compress(text, target_ratio=0.5)\nexcept TypeError as e:\n    if \"'compressed' must be a string\" in str(e):\n        result = local_fallback(text)  # or pass through uncompressed\n    else:\n        raise","preventionTips":["Add a startup health check that POSTs a sample payload and validates the response schema","Pin client and server versions together; deploy them in lockstep","Monitor for non-200/non-JSON responses from intermediaries in front of the Kompress service"],"tags":["network","api-contract","json","kompress","remote-backend"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}