nexu-io/open-design · error · ValueError

Unsupported web backend: {backend!r}

Error message

Unsupported web backend: {backend!r}

What it means

Final guard in web_search(): if backend is not one of auto/brave/exa/serper/parallel/none, raise ValueError(f"Unsupported web backend: {backend!r}"). Unlike the RuntimeError siblings this is a ValueError (programming/contract error, not a missing secret). The dispatcher normalizes 'auto' before this point, so reaching here means an explicit, unrecognized backend string was forced.

Source

Thrown at design-templates/last30days/scripts/lib/grounding.py:229

            raise RuntimeError("BRAVE_API_KEY is required when web_backend='brave'")
        return brave_search(query, date_range, key)
    if backend == "exa":
        key = config.get("EXA_API_KEY")
        if not key:
            raise RuntimeError("EXA_API_KEY is required when web_backend='exa'")
        return exa_search(query, date_range, key)
    if backend == "serper":
        key = config.get("SERPER_API_KEY")
        if not key:
            raise RuntimeError("SERPER_API_KEY is required when web_backend='serper'")
        return serper_search(query, date_range, key)
    if backend == "parallel":
        key = config.get("PARALLEL_API_KEY")
        if not key:
            raise RuntimeError("PARALLEL_API_KEY is required when web_backend='parallel'")
        return parallel_search(query, date_range, key)
    if backend != "none":
        raise ValueError(f"Unsupported web backend: {backend!r}")
    return [], {}


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _normalize_date(value: object) -> str | None:
    if value is None:
        return None
    parsed = dates.parse_date(str(value).strip())
    if not parsed:
        return None
    return parsed.date().isoformat()


def _serper_date_param(iso_date: str) -> str:
    """Convert YYYY-MM-DD to MM/DD/YYYY for Serper tbs parameter."""

View on GitHub (pinned to 5be4028344)

Solutions

  1. Use one of: auto, none, brave, exa, serper, parallel.
  2. Spell it lowercase — the comparison is exact.
  3. Prefer 'auto' to let the code choose based on which key is set.

Example fix

# before
python3.12 last30days.py --topic x --web-backend Bing
# after
python3.12 last30days.py --topic x --web-backend auto
Defensive patterns

Strategy: type-guard

Validate before calling

SUPPORTED_BACKENDS = {'auto', 'none', 'brave', 'exa', 'serper', 'parallel'}
if web_backend not in SUPPORTED_BACKENDS:
    raise ValueError(f'web_backend must be one of {sorted(SUPPORTED_BACKENDS)}, got {web_backend!r}')

Type guard

def is_supported_backend(name: object) -> bool:
    return isinstance(name, str) and name in {'auto','none','brave','exa','serper','parallel'}

Prevention

When it happens

Trigger: `--web-backend bing`, `--web-backend Google`, `--web-backend ""` after the auto check, or any value not in the supported set passed programmatically.

Common situations: Typo in the backend name; case mismatch (the dispatcher does not lowercase); a stale value from old docs; passing an empty string explicitly.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/4df722cdd408b5e1. Report an issue: GitHub.