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
- Set PARALLEL_API_KEY in the environment or last30days .env and re-run.
- Or drop the explicit web_backend='parallel' selection to fall back to automatic resolution.
- 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
- Register the Parallel key before flipping web_backend to 'parallel'.
- Rotate keys by editing the .env in place, never by deleting the entry while the flag remains.
- Log a masked key presence check at app start for every forced backend.
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
- BRAVE_API_KEY is required when web_backend='brave'
- EXA_API_KEY is required when web_backend='exa'
- SERPER_API_KEY is required when web_backend='serper'
- Unsupported web backend: {backend!r}
- Gemini HTTP {exc.code}: {detail}
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/d9dc5f8becaa1326.
Report an issue: GitHub.