{"record":{"id":"3b179c55101ff2f5","repo":"mvanhorn/last30days-skill","slug":"exa-api-key-is-required-when-web-backend-exa","errorCode":null,"errorMessage":"EXA_API_KEY is required when web_backend='exa'","messagePattern":"EXA_API_KEY is required when web_backend='exa'","errorType":"validation","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/grounding.py","lineNumber":259,"sourceCode":"            backend = \"parallel\"\n        elif env.keyless_web_allowed(config):\n            # No paid key and the host has no native search -> use the keyless\n            # floor. On a native-search host this branch is skipped (the model\n            # supplies web results itself), so the engine returns nothing here.\n            backend = \"keyless\"\n        else:\n            return [], {}\n    items: list[dict] = []\n    artifact: dict = {}\n    if backend == \"brave\":\n        key = config.get(\"BRAVE_API_KEY\")\n        if not key:\n            raise RuntimeError(\"BRAVE_API_KEY is required when web_backend='brave'\")\n        items, artifact = brave_search(query, date_range, key)\n    elif backend == \"exa\":\n        key = config.get(\"EXA_API_KEY\")\n        if not key:\n            raise RuntimeError(\"EXA_API_KEY is required when web_backend='exa'\")\n        items, artifact = exa_search(query, date_range, key)\n    elif backend == \"serper\":\n        key = config.get(\"SERPER_API_KEY\")\n        if not key:\n            raise RuntimeError(\"SERPER_API_KEY is required when web_backend='serper'\")\n        items, artifact = serper_search(query, date_range, key)\n    elif backend == \"parallel\":\n        key = config.get(\"PARALLEL_API_KEY\")\n        if not key:\n            raise RuntimeError(\"PARALLEL_API_KEY is required when web_backend='parallel'\")\n        items, artifact = parallel_search(query, date_range, key)\n    elif backend == \"keyless\":\n        items, artifact = web_search_keyless.keyless_search(query, date_range, config)\n    elif backend != \"none\":\n        raise ValueError(f\"Unsupported web backend: {backend!r}\")\n    else:\n        return [], {}\n    if items and not _reddit_excluded(config):","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/grounding.py#L241-L277","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nexport WEB_BACKEND=exa    # EXA_API_KEY missing\n\n# after\nexport EXA_API_KEY=<your-key>\nexport WEB_BACKEND=exa","handlingStrategy":"validation","validationCode":"config = load_config()\nif config.get(\"web_backend\") == \"exa\":\n    assert config.get(\"EXA_API_KEY\"), \"EXA_API_KEY must be set for exa backend\"","typeGuard":null,"tryCatchPattern":"try:\n    grounding.web_search_with_backend(query, config)\nexcept RuntimeError as e:\n    if 'EXA_API_KEY' in str(e):\n        raise SystemExit('set EXA_API_KEY or remove web_backend=exa') from e","preventionTips":["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."],"tags":["config","api-key","web-search","exa"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}