{"record":{"id":"8b60e3861ab0cfd0","repo":"headroomlabs-ai/headroom","slug":"hash-required","errorCode":null,"errorMessage":"hash required","messagePattern":"hash required","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"headroom/proxy/server.py","lineNumber":4603,"sourceCode":"    @app.post(\"/v1/retrieve\", dependencies=[Depends(_require_loopback)])\n    async def ccr_retrieve(request: Request):\n        \"\"\"Retrieve original content from CCR compression cache.\n\n        This is the \"Retrieve\" part of CCR (Compress-Cache-Retrieve).\n        When SmartCrusher compresses tool outputs, the original data is cached.\n        LLMs can call this endpoint to get more data if needed.\n\n        Request body:\n            hash (str): Hash key from compression marker (required)\n\n        Response:\n            {\"hash\": \"...\", \"original_content\": \"...\", ...}\n        \"\"\"\n        data = await request.json()\n        hash_key = data.get(\"hash\")\n\n        if not hash_key:\n            raise HTTPException(status_code=400, detail=\"hash required\")\n\n        store = get_compression_store()\n\n        entry_status = store.get_entry_status(hash_key, clean_expired=True)\n        if entry_status[\"status\"] != \"available\":\n            raise HTTPException(\n                status_code=404,\n                detail=format_retrieval_miss_detail(entry_status),\n            )\n\n        # Retrieval is by hash: always return the full original content.\n        entry = store.retrieve(hash_key)\n        if entry:\n            return {\n                \"hash\": hash_key,\n                \"original_content\": entry.original_content,\n                \"original_tokens\": entry.original_tokens,\n                \"original_item_count\": entry.original_item_count,","sourceCodeStart":4585,"sourceCodeEnd":4621,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/proxy/server.py#L4585-L4621","documentation":"POST /v1/retrieve requires a JSON body containing a non-empty 'hash' field; missing, null, or empty hash returns HTTP 400 'hash required'. The hash key is the compression marker identifier the LLM received when content was compressed.","triggerScenarios":"Posting {} to /v1/retrieve, or a body where 'hash' is null, empty string, or 0.","commonSituations":"Agent frameworks hand-rolling tool-call bodies instead of using the provided tool-call endpoint; malformed JSON field names like 'hash_key'; LLM omitting the parameter.","solutions":["Include the exact compression marker hash: {\"hash\": \"<value>\"}.","Use /v1/retrieve/tool_call to parse provider tool-call formats automatically.","Validate the marker payload before sending."],"exampleFix":"# before\ncurl -X POST /v1/retrieve -d '{}'\n\n# after\ncurl -X POST /v1/retrieve -H 'Content-Type: application/json' -d '{\"hash\": \"abc123\"}'","handlingStrategy":"validation","validationCode":"payload = {\"hash\": marker_hash}\nif not isinstance(marker_hash, str) or not marker_hash:\n    raise ValueError(\"marker must carry a non-empty hash\")","typeGuard":"def has_hash(payload: dict) -> bool:\n    return bool(isinstance(payload.get(\"hash\"), str) and payload[\"hash\"].strip())","tryCatchPattern":"resp = await client.post(\"/v1/retrieve\", json=payload)\nif resp.status_code == 400:\n    raise RuntimeError(f\"bad retrieval request: {resp.text}\")","preventionTips":["Propagate the compression marker's hash verbatim.","Prefer the tool_call endpoint for provider-native payloads."],"tags":["http","validation","retrieval"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}