headroomlabs-ai/headroom · error · HTTPException

Invalid tool call or not a {CCR_TOOL_NAME} call

Error message

Invalid tool call or not a {CCR_TOOL_NAME} call

What it means

POST /v1/retrieve/tool_call parses the submitted tool_call for the given provider (default anthropic) and returns HTTP 400 when parsing yields no hash — the payload is malformed or is not a headroom_retrieve (CCR_TOOL_NAME) call. This is request-shape validation, not a store miss.

Source

Thrown at headroom/proxy/server.py:4999

                "provider": "openai"
            }

        Response:
            {
                "tool_result": {...},  # Formatted for the provider
                "success": true,
                "data": {...}  # Raw retrieval data
            }
        """
        data = await request.json()
        tool_call = data.get("tool_call", {})
        provider = data.get("provider", "anthropic")

        # Parse the tool call
        hash_key = parse_tool_call(tool_call, provider)

        if hash_key is None:
            raise HTTPException(
                status_code=400, detail=f"Invalid tool call or not a {CCR_TOOL_NAME} call"
            )

        # Perform retrieval
        store = get_compression_store()
        entry_status = store.get_entry_status(hash_key, clean_expired=True)

        if entry_status["status"] != "available":
            retrieval_data = {
                "error": format_retrieval_miss_detail(entry_status),
                "hash": hash_key,
                "status": entry_status["status"],
                "ttl_seconds": entry_status.get("ttl_seconds", entry_status["default_ttl_seconds"]),
            }
        else:
            # Retrieval is by hash: always return the full original content.
            entry = store.retrieve(hash_key)
            if entry:

View on GitHub (pinned to 322425c43b)

Solutions

  1. Send only headroom_retrieve tool calls to this endpoint.
  2. Match the provider field to the actual payload ('anthropic', 'openai', ...).
  3. Ensure the tool-call arguments include the compression hash.
  4. For raw hashes, use POST /v1/retrieve with {"hash": ...} instead.

Example fix

# before
{"tool_call": {"name": "web_search", "arguments": {}}, "provider": "anthropic"}

# after
{"tool_call": {"name": "headroom_retrieve", "arguments": {"hash": "abc123"}}, "provider": "anthropic"}
Defensive patterns

Strategy: validation

Validate before calling

def is_retrieve_call(tool_call: dict) -> bool:
    return tool_call.get("name") == "headroom_retrieve" and bool(
        (tool_call.get("arguments") or {}).get("hash")
    )

Type guard

def is_retrieve_call(tc: dict) -> bool:
    name = tc.get("name") or tc.get("function", {}).get("name")
    return name == "headroom_retrieve"

Try / catch

resp = await client.post("/v1/retrieve/tool_call", json=payload)
if resp.status_code == 400:
    route_elsewhere(payload)  # not a headroom_retrieve call; don't retry

Prevention

When it happens

Trigger: Posting a tool_call for a different tool name, missing the hash argument, wrong provider string so the parse format mismatches, or an arbitrary JSON object.

Common situations: Agent frameworks forwarding every LLM tool call indiscriminately; provider field not matching the actual tool_call format; schema drift in tool-call JSON.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/1aaf11cf4e55af19. Report an issue: GitHub.