mvanhorn/last30days-skill · error · RuntimeError

PARALLEL_API_KEY is required when web_backend='parallel'

Error message

PARALLEL_API_KEY is required when web_backend='parallel'

What it means

Raised by grounding.py when the resolved backend is 'parallel' (search.parallel.ai) but PARALLEL_API_KEY is missing from config. Same contract as brave/exa/serper: an explicitly chosen paid backend requires its key, otherwise RuntimeError. The 'keyless' and 'none' backends are the only paths that never need a key.

Source

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

    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.
        with http.capture_failures():
            items = _enrich_reddit_items(items)
    return items, artifact

View on GitHub (pinned to c7460f6114)

Solutions

  1. Set PARALLEL_API_KEY in the environment or last30days .env and re-run.
  2. Or drop the explicit web_backend='parallel' selection to fall back to automatic resolution.
  3. Validate the config as the engine sees it (mask values) to confirm the key resolves.

Example fix

# before
export WEB_BACKEND=parallel   # no PARALLEL_API_KEY

# after
export PARALLEL_API_KEY=<your-key>
export WEB_BACKEND=parallel
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

try:
    grounding.web_search_with_backend(query, config)
except RuntimeError as e:
    if 'PARALLEL_API_KEY' in str(e):
        # env/config fix; do not retry — it will fail identically
        ...

Prevention

When it happens

Trigger: web_backend='parallel' with PARALLEL_API_KEY unset; the key was rotated/revoked and removed from .env but the backend flag remained; env var defined with surrounding quotes or spaces making it empty after parsing.

Common situations: Adopting the Parallel backend after seeing it in CONFIGURATION.md without completing signup; orchestrators that strip unknown env vars; the flag set globally in a shared config while the key is per-machine.

Related errors


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