Skip to content

Vendored tavily/exa web-search runtimes fail to read process.env API keys despite keys being demonstrably present #76491

Description

@liemnhoang

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.apiKeyprocess.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

  1. 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.
  2. Restart the gateway.
  3. 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.
  4. Run: openclaw infer web search --provider tavily --query 'test' --limit 1
  5. Expected: real Tavily search results.
  6. 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.
  7. 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

  1. 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.
  2. Affects exa equivalently — dist/exa-web-search-provider.runtime-CQwW9w5r.js:30-36 has the same fallback structure.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions