crewAIInc/crewAI · error · ValueError

Invalid headers: {e}

Error message

Invalid headers: {e}

What it means

BraveSearchTool normalizes custom headers (lowercased, with x-subscription-token and accept injected) and validates the merged dict against a Pydantic header_schema. Any schema violation — wrong value types, unexpected header names the schema rejects, or malformed values — is re-raised as ValueError('Invalid headers: ...') with the underlying validation error attached. This happens in the constructor and in update_headers().

Source

Thrown at lib/crewai-tools/src/crewai_tools/tools/brave_search_tool/base.py:161

    @property
    def headers(self) -> dict[str, Any]:
        return self._headers

    def set_headers(self, headers: dict[str, Any]) -> BraveSearchToolBase:
        merged = {**self._headers, **{k.lower(): v for k, v in headers.items()}}
        self._headers = self._build_and_validate_headers(merged)
        return self

    def _build_and_validate_headers(self, headers: dict[str, Any]) -> dict[str, Any]:
        normalized = {k.lower(): v for k, v in headers.items()}
        normalized.setdefault("x-subscription-token", self._api_key)
        normalized.setdefault("accept", "application/json")

        try:
            self.header_schema(**normalized)
        except Exception as e:
            raise ValueError(f"Invalid headers: {e}") from e

        return normalized

    def _rate_limit(self) -> None:
        """Enforce minimum interval between requests for this instance. Thread-safe."""
        if self._requests_per_second <= 0:
            return

        min_interval = 1.0 / self._requests_per_second
        with self._rate_limit_lock:
            now = time.time()
            next_allowed = self._last_request_time + min_interval
            if now < next_allowed:
                time.sleep(next_allowed - now)
                now = time.time()
            self._last_request_time = now

    def _make_request(

View on GitHub (pinned to 754d7323be)

Solutions

  1. Read the chained validation error text after 'Invalid headers:' — it names the exact failing field and reason.
  2. Ensure all header keys and values are plain strings (cast: {str(k): str(v) for k, v in headers.items()}).
  3. Only override headers the schema supports; for auth, rely on the api_key/BRAVE_API_KEY mechanism rather than hand-building the token header.
  4. If you need x-subscription-token specifically, pass the key via api_key and let the tool inject it.

Example fix

# before
tool = BraveSearchTool(api_key=key, headers={"X-Subscription-Token-Priority": "user"})

# after
tool = BraveSearchTool(api_key=key, headers={"x-subscription-token-priority": "user"})  # valid schema field, lowercase str values
Defensive patterns

Strategy: validation

Validate before calling

def sane_headers(headers: dict) -> dict:
    return {str(k).lower(): str(v) for k, v in headers.items() if k and v is not None}

Type guard

def is_str_str_dict(d: object) -> bool:
    return isinstance(d, dict) and all(
        isinstance(k, str) and isinstance(v, str) and k and v for k, v in d.items()
    )

Try / catch

try:
    tool = BraveSearchTool(api_key=key, headers=custom)
except ValueError as e:
    if "Invalid headers" in str(e):
        tool = BraveSearchTool(api_key=key)  # retry with defaults; add headers back one at a time
    else:
        raise

Prevention

When it happens

Trigger: Passing headers={'X-Subscription-Token': 12345} (int instead of str); custom headers whose names or types the header_schema does not accept; header values containing newlines or non-ASCII that fail validation; calling update_headers() with similarly invalid entries.

Common situations: Reading header values from config/env without casting to str; passing an Authorization header the schema forbids; dict-of-dicts or None values from JSON config.

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/8721e3a62719ccae. Report an issue: GitHub.