hermes(chat): make cluster_read robust to large node listings

Node .status.images (every cached image on the node) overflowed the
size cap and left json.loads parsing a truncated blob, so a nodes query
came back as a non-JSON error. The de-noise pass now summarizes that
list, and genuine truncation returns the readable prefix with a
narrow-your-query hint instead of an error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
This commit is contained in:
jenkins 2026-08-24 14:40:09 -03:00
parent 4ccf2066ca
commit ed0bc30d7c
2 changed files with 25 additions and 7 deletions

View File

@ -119,6 +119,11 @@ def _strip_noise(payload: Any) -> Any:
if isinstance(annotations, dict):
for key in [k for k in annotations if "last-applied" in k]:
annotations.pop(key, None)
status = payload.get("status")
if isinstance(status, dict) and isinstance(status.get("images"), list):
# Node .status.images is a huge blob of every cached image; it
# never helps the model and routinely overflows the size cap.
status["images"] = f"[{len(status['images'])} cached images omitted]"
for value in payload.values():
_strip_noise(value)
elif isinstance(payload, list):
@ -149,17 +154,20 @@ def _handle_cluster_read(args: dict[str, Any] | None = None, **kwargs: Any) -> s
return json.dumps({"error": f"cluster API returned {error.code}", "detail": detail[:400]})
except Exception as error: # noqa: BLE001 - tool result, never an exception into the loop
return json.dumps({"error": f"cluster API unavailable: {type(error).__name__}"})
truncated = len(raw) > MAX_RESPONSE_BYTES
over_wire = len(raw) > MAX_RESPONSE_BYTES
if over_wire:
# A truncated wire body is not parseable JSON; hand back the readable
# prefix rather than failing, so a large list still yields something.
return json.dumps({"truncated": True, "reason": "response exceeded size cap; narrow with namespace/name/label_selector",
"partial": raw[: MAX_RESPONSE_BYTES - 96].decode("utf-8", "replace")})
try:
payload = _strip_noise(json.loads(raw[:MAX_RESPONSE_BYTES] if truncated else raw))
payload = _strip_noise(json.loads(raw))
except ValueError:
return json.dumps({"error": "cluster API returned non-JSON data"})
body = json.dumps(payload, separators=(",", ":"))
if len(body) > MAX_RESPONSE_BYTES:
body = body[:MAX_RESPONSE_BYTES]
truncated = True
if truncated:
return json.dumps({"truncated": True, "partial": body[: MAX_RESPONSE_BYTES - 64]})
return json.dumps({"truncated": True, "reason": "result exceeded size cap after cleanup; narrow the query",
"partial": body[: MAX_RESPONSE_BYTES - 96]})
return body

View File

@ -86,7 +86,17 @@ def test_handler_is_get_only_and_bounded(monkeypatch, tmp_path):
monkeypatch.setattr(mod.urllib.request, "urlopen", huge_open)
out = json.loads(mod._handle_cluster_read({"resource": "pods"}))
assert out.get("truncated") is True or out.get("error")
assert out.get("truncated") is True and "partial" in out
def node_open(request, timeout=None, context=None):
payload = {"kind": "NodeList", "items": [{"metadata": {"name": "n"},
"status": {"images": [{"names": ["x"], "sizeBytes": 1} for _ in range(500)]}}]}
return FakeResponse(json.dumps(payload).encode())
monkeypatch.setattr(mod.urllib.request, "urlopen", node_open)
out = json.loads(mod._handle_cluster_read({"resource": "nodes"}))
assert out["kind"] == "NodeList"
assert "cached images omitted" in json.dumps(out)
def test_errors_never_raise(monkeypatch, tmp_path):