Summary
Vendored tavily and exa plugins fail with "web_search (tavily) needs a Tavily API key..." (and equivalent for exa) even when process.env.TAVILY_API_KEY / process.env.EXA_API_KEY are confirmed present in the gateway's Node process.env.
The plugin code's static fallback chain (cfg.plugins.entries.tavily.config.webSearch.apiKey → process.env.TAVILY_API_KEY → undefined) is correct in isolation — a node-level isolated probe of the same compiled module returns 401 from Tavily's API when process.env.TAVILY_API_KEY is set to a bogus value, proving the env-fallback path works at the module level. But inside the actual gateway runtime, the same plugin invoking the same code path doesn't see the value.
Most likely cause: worker-thread or vm-context isolation in the web-search runtime that holds a separate process.env snapshot from the main gateway process.
Reproducer
- Move all secrets out of
openclaw.json § env (e.g. set "env": {}) into a systemd EnvironmentFile so they enter the gateway via process.env at spawn rather than via openclaw.json env-block hydration.
- Restart the gateway.
- Verify
process.env.TAVILY_API_KEY is set in the gateway's process.env:
- Kernel-level:
cat /proc/$(systemctl --user show openclaw-gateway -p MainPID --value)/environ | tr '\0' '\n' | grep TAVILY_API_KEY → returns the key=value line.
- Node-level (diagnostic plugin):
kind: "hooks", subscribe to gateway_start, dump Object.keys(process.env) to a file. Verify TAVILY_API_KEY is in the list.
- Run:
openclaw infer web search --provider tavily --query 'test' --limit 1
- Expected: real Tavily search results.
- Actual:
Error: web_search (tavily) needs a Tavily API key. Set TAVILY_API_KEY in the Gateway environment, or configure plugins.entries.tavily.config.webSearch.apiKey.
- Same applies to
--provider exa with corresponding EXA_API_KEY.
Evidence — code path is correct in isolation
Vendored tavily resolver at dist/tavily-client-C5ATNBVl.js:23-25:
function resolveTavilyApiKey(cfg) {
return normalizeConfiguredSecret(resolveTavilySearchConfig(cfg)?.apiKey, "plugins.entries.tavily.config.webSearch.apiKey")
|| normalizeSecretInput(process.env.TAVILY_API_KEY)
|| void 0;
}
Isolated node probe importing the same compiled module:
process.env.TAVILY_API_KEY unset → KEY-NOT-FOUND (matches the user-reported error)
process.env.TAVILY_API_KEY set to bogus key → OTHER-ERROR (key was found, HTTP/other failed): Tavily Search API error (401)
So the process.env fallback does fire when called outside the gateway runtime context.
Evidence — env IS populated inside the gateway
Diagnostic plugin (gateway_start handler) confirms all 25 managed env keys present including TAVILY_API_KEY and EXA_API_KEY. Yet vendored tavily/exa report missing.
Workaround (current)
Disable vendored tavily and exa in openclaw.json § plugins.entries:
"tavily": { "enabled": false },
"exa": { "enabled": false }
User-owned openclaw-tavily and openclaw-exa plugins work correctly under the same env conditions. But this loses the openclaw infer web search --provider {tavily,exa} CLI surface.
Suggested investigation
- Where does the web-search provider runtime execute? Check
provider-web-search-Bm89LMpy.js, web-search-provider-common-DIMdPurF.js, and any worker-thread / vm-context spawn pattern.
- Affects exa equivalently —
dist/exa-web-search-provider.runtime-CQwW9w5r.js:30-36 has the same fallback structure.
- Confirm whether vendored providers using
cfg-only paths are unaffected — would localize the bug to the env-fallback specifically.
Environment
- OpenClaw version: 2026.4.29 (build a448042)
- Node: v22.22.0
- OS: WSL2 Ubuntu on Windows 11 Pro 26200
- Install: npm global at /home/micha/.npm-global/lib/node_modules/openclaw
- Service: systemd user unit (openclaw-gateway.service)
- Discovered during: in-house quarterly audit
Summary
Vendored
tavilyandexaplugins fail with"web_search (tavily) needs a Tavily API key..."(and equivalent for exa) even whenprocess.env.TAVILY_API_KEY/process.env.EXA_API_KEYare confirmed present in the gateway's Nodeprocess.env.The plugin code's static fallback chain (
cfg.plugins.entries.tavily.config.webSearch.apiKey→process.env.TAVILY_API_KEY→ undefined) is correct in isolation — a node-level isolated probe of the same compiled module returns 401 from Tavily's API whenprocess.env.TAVILY_API_KEYis set to a bogus value, proving the env-fallback path works at the module level. But inside the actual gateway runtime, the same plugin invoking the same code path doesn't see the value.Most likely cause: worker-thread or vm-context isolation in the web-search runtime that holds a separate
process.envsnapshot from the main gateway process.Reproducer
openclaw.json § env(e.g. set"env": {}) into a systemdEnvironmentFileso they enter the gateway viaprocess.envat spawn rather than via openclaw.json env-block hydration.process.env.TAVILY_API_KEYis set in the gateway'sprocess.env:cat /proc/$(systemctl --user show openclaw-gateway -p MainPID --value)/environ | tr '\0' '\n' | grep TAVILY_API_KEY→ returns the key=value line.kind: "hooks", subscribe togateway_start, dumpObject.keys(process.env)to a file. VerifyTAVILY_API_KEYis in the list.openclaw infer web search --provider tavily --query 'test' --limit 1Error: web_search (tavily) needs a Tavily API key. Set TAVILY_API_KEY in the Gateway environment, or configure plugins.entries.tavily.config.webSearch.apiKey.--provider exawith correspondingEXA_API_KEY.Evidence — code path is correct in isolation
Vendored tavily resolver at
dist/tavily-client-C5ATNBVl.js:23-25:Isolated node probe importing the same compiled module:
process.env.TAVILY_API_KEYunset →KEY-NOT-FOUND(matches the user-reported error)process.env.TAVILY_API_KEYset to bogus key →OTHER-ERROR (key was found, HTTP/other failed): Tavily Search API error (401)So the
process.envfallback does fire when called outside the gateway runtime context.Evidence — env IS populated inside the gateway
Diagnostic plugin (
gateway_starthandler) confirms all 25 managed env keys present includingTAVILY_API_KEYandEXA_API_KEY. Yet vendored tavily/exa report missing.Workaround (current)
Disable vendored
tavilyandexainopenclaw.json § plugins.entries:User-owned
openclaw-tavilyandopenclaw-exaplugins work correctly under the same env conditions. But this loses theopenclaw infer web search --provider {tavily,exa}CLI surface.Suggested investigation
provider-web-search-Bm89LMpy.js,web-search-provider-common-DIMdPurF.js, and any worker-thread / vm-context spawn pattern.dist/exa-web-search-provider.runtime-CQwW9w5r.js:30-36has the same fallback structure.cfg-only paths are unaffected — would localize the bug to the env-fallback specifically.Environment