unclecode/crawl4ai · error · ValueError

invalid header name {name!r}

Error message

invalid header name {name!r}

What it means

SetHeadersParams' validator enforces _HEADER_NAME_RE = ^[A-Za-z0-9-]{1,64}$ on every header name. Names with spaces, underscores, colons, unicode, or longer than 64 chars are rejected. This keeps header injection and malformed Chromium CDP headers out of declarative hook configs.

Source

Thrown at deploy/docker/hook_registry.py:76

    secure: bool = True
    httpOnly: bool = False


class AddCookiesParams(BaseModel):
    cookies: List[_Cookie] = Field(..., min_length=1, max_length=_MAX_COOKIES)


class SetHeadersParams(BaseModel):
    headers: Dict[str, str]

    @field_validator("headers")
    @classmethod
    def _check(cls, v):
        if len(v) > _MAX_HEADERS:
            raise ValueError(f"too many headers (max {_MAX_HEADERS})")
        for name, value in v.items():
            if not _HEADER_NAME_RE.match(name):
                raise ValueError(f"invalid header name {name!r}")
            if any(c in value for c in "\r\n\x00"):
                raise ValueError(f"control characters in value for header {name!r}")
        return v


class ScrollToBottomParams(BaseModel):
    max_steps: int = Field(10, ge=1, le=_MAX_SCROLL_STEPS)
    delay_ms: int = Field(500, ge=0, le=_MAX_SCROLL_DELAY_MS)


class WaitForTimeoutParams(BaseModel):
    timeout_ms: int = Field(..., ge=0, le=_MAX_WAIT_MS)


# ───────────────────────── server-authored hook factories ─────────────────────────
def _factory_block_resources(p: BlockResourcesParams):
    types = set(p.resource_types)

View on GitHub (pinned to 7e80152142)

Solutions

  1. Use the exact HTTP token form: letters, digits, and hyphens only, e.g. 'User-Agent', 'X-Request-Id'.
  2. Strip any trailing ': value' when converting from curl syntax.
  3. Replace underscores with hyphens (X_Custom_Header -> X-Custom-Header).

Example fix

# before
{"headers": {"User-Agent: Mobile": "UA", "X_Custom": "1"}}

# after
{"headers": {"User-Agent": "Mobile UA", "X-Custom": "1"}}
Defensive patterns

Strategy: validation

Validate before calling

import re
HEADER_NAME_RE = re.compile(r"^[A-Za-z0-9-]{1,64}$")

def valid_header_names(spec: dict) -> bool:
    h = spec.get("params", {}).get("headers", {})
    return all(HEADER_NAME_RE.match(k) for k in h)

Type guard

def is_http_token_name(name) -> bool:
    return isinstance(name, str) and bool(re.fullmatch(r"[A-Za-z0-9-]{1,64}", name))

Try / catch

try:
    hooks = build_declarative_hooks(specs)
except HookValidationError as e:
    raise ConfigError(str(e)) from e  # message names the offending header

Prevention

When it happens

Trigger: Passing 'User-Agent: Mobile' (colon included), 'X_Custom_Header' (underscore), 'Accept Encoding' (space), a non-ASCII header name, or a name over 64 characters in set_headers params.

Common situations: Copy-pasting headers from curl -H syntax ('Header: value') into the name field; using underscores because proxies like nginx historically accept them; HTTP/2 lowercase names are fine but separators must be hyphens.

Related errors


AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14). Data as JSON: /api/errors/6f6caf459cb0598d. Report an issue: GitHub.