mvanhorn/last30days-skill · error · RuntimeError
EXA_API_KEY is required when web_backend='exa'
Error message
EXA_API_KEY is required when web_backend='exa'
What it means
Raised by grounding.py when the resolved web backend is 'exa' but EXA_API_KEY is absent from the config. Exa is one of the paid-key backends (brave/exa/serper/parallel); selecting it explicitly is a promise that a key exists, so a missing key is a hard RuntimeError rather than a silent empty result set. The keyless floor is only used when no paid key is configured AND web_backend was not forced.
Source
Thrown at skills/last30days/scripts/lib/grounding.py:259
backend = "parallel"
elif env.keyless_web_allowed(config):
# No paid key and the host has no native search -> use the keyless
# floor. On a native-search host this branch is skipped (the model
# supplies web results itself), so the engine returns nothing here.
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):View on GitHub (pinned to c7460f6114)
Solutions
- Set EXA_API_KEY (Exa dashboard) in the environment or the last30days .env file and re-run.
- Or unset web_backend so resolution picks a backend you do have a key for, or the keyless floor.
- Confirm the harness/subprocess environment actually contains the variable (printenv EXA_API_KEY) before blaming the config file.
Example fix
# before export WEB_BACKEND=exa # EXA_API_KEY missing # after export EXA_API_KEY=<your-key> export WEB_BACKEND=exa
Defensive patterns
Strategy: validation
Validate before calling
config = load_config()
if config.get("web_backend") == "exa":
assert config.get("EXA_API_KEY"), "EXA_API_KEY must be set for exa backend" Try / catch
try:
grounding.web_search_with_backend(query, config)
except RuntimeError as e:
if 'EXA_API_KEY' in str(e):
raise SystemExit('set EXA_API_KEY or remove web_backend=exa') from e Prevention
- Pair each explicit backend choice with its key in the same .env commit.
- For CI, fail the workflow early if WEB_BACKEND is set but the matching key secret is missing.
- Remember keyless floor and 'none' need no key — use those for key-free environments.
When it happens
Trigger: web_backend='exa' via flag/config while EXA_API_KEY is unset or empty; key stored in a project .env that is not on the load path; the variable was renamed or never obtained from Exa.
Common situations: Onboarding steps skipped: the user picked Exa as backend before completing key setup; CI has the flag in config but secrets are injected under a different name; switching backends without switching keys.
Related errors
- BRAVE_API_KEY is required when web_backend='brave'
- SERPER_API_KEY is required when web_backend='serper'
- PARALLEL_API_KEY is required when web_backend='parallel'
- 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/3b179c55101ff2f5.
Report an issue: GitHub.