mvanhorn/last30days-skill · error · RuntimeError

SERPER_API_KEY is required when web_backend='serper'

Error message

SERPER_API_KEY is required when web_backend='serper'

What it means

Raised by grounding.py when web_backend resolves to 'serper' but SERPER_API_KEY is not present in the config dict. Like the other paid-key backends, serper is only ever entered by explicit selection, so the dispatcher treats a missing key as a configuration error (RuntimeError) instead of degrading to no results.

Source

Thrown at skills/last30days/scripts/lib/grounding.py:264

            backend = "keyless"
        else:
            return [], {}
    items: list[dict] = []
    artifact: dict = {}
    if backend == "brave":
        key = config.get("BRAVE_API_KEY")
        if not key:
            raise RuntimeError("BRAVE_API_KEY is required when web_backend='brave'")
        items, artifact = brave_search(query, date_range, key)
    elif backend == "exa":
        key = config.get("EXA_API_KEY")
        if not key:
            raise RuntimeError("EXA_API_KEY is required when web_backend='exa'")
        items, artifact = exa_search(query, date_range, key)
    elif backend == "serper":
        key = config.get("SERPER_API_KEY")
        if not key:
            raise RuntimeError("SERPER_API_KEY is required when web_backend='serper'")
        items, artifact = serper_search(query, date_range, key)
    elif backend == "parallel":
        key = config.get("PARALLEL_API_KEY")
        if not key:
            raise RuntimeError("PARALLEL_API_KEY is required when web_backend='parallel'")
        items, artifact = parallel_search(query, date_range, key)
    elif backend == "keyless":
        items, artifact = web_search_keyless.keyless_search(query, date_range, config)
    elif backend != "none":
        raise ValueError(f"Unsupported web backend: {backend!r}")
    else:
        return [], {}
    if items and not _reddit_excluded(config):
        # Reddit enrichment is a best-effort secondary fetch on already-retrieved
        # web results. Isolate its HTTP failures in a throwaway capture sink so a
        # reddit.com fetch failure (e.g. a 403 on a datacenter IP) is not
        # attributed to the web/grounding source itself — which would otherwise
        # discard the successfully retrieved results and report the source failed.

View on GitHub (pinned to c7460f6114)

Solutions

  1. Add SERPER_API_KEY to the environment or last30days .env and re-run.
  2. If the key is not available, remove the explicit web_backend setting and let automatic resolution choose an available backend.
  3. Double-check spelling: it must be SERPER_API_KEY exactly.

Example fix

# before
# .env has SERPER_KEY=...

# after
# .env has SERPER_API_KEY=<your-key>
Defensive patterns

Strategy: validation

Validate before calling

config = load_config()
if config.get("web_backend") == "serper":
    assert config.get("SERPER_API_KEY"), "SERPER_API_KEY must be set for serper backend"

Try / catch

try:
    grounding.web_search_with_backend(query, config)
except RuntimeError as e:
    if 'SERPER_API_KEY' in str(e):
        # fix env spelling (SERPER_API_KEY, not SERPER_KEY) or unset the backend
        ...

Prevention

When it happens

Trigger: web_backend='serper' with SERPER_API_KEY unset/empty; key present only in the interactive shell but the engine is spawned by an agent gateway with a limited PATH/env; stale .env cached from before the key was added.

Common situations: User copies a recommended config that names serper.dev but never registers a key; the .env was written to the wrong directory (not ~/.config/last30days or LAST30DAYS_STORE); a typo like SERPER_KEY.

Related errors


AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15). Data as JSON: /api/errors/f958214932185760. Report an issue: GitHub.