#Agent started lying about tasks!

1 messages · Page 1 of 1 (latest)

weak quest
#

I set up OpenClaw locally last week, and it worked well, but I needed it on all the time so got a VPS setup on Sunday. Installed using openai-codex/gpt-5.3-codex model (as per the local version). Set up a workflow Monday so it grabs an article form the wen when I send it, formats it and posts it to a CMS using an API. Creates an article, menu item, tags, selects an image. All great and really happy with how it got things configured. Tuesday I messed about with adding google/gemini-3-pro-preview as a fallback and also main if I needed to swop them over. That's when things seemed to start going wrong. gemini-3-pro-preview was slower and was stopping due to high usage, so I set gpt-5.3-codex back as the main.

Now it won't implement anyuthing live. It SAYS it can and will do the tasks we had running before. It will SAY it's done them. But when I check the website has not been updated. If I ask why it didn't do it then it makes an excuse like 'sorry, was waiting for confirmation' or similar. After it has confirmed doing the task I can ask it to go and check the article ID that it created, and yes it goes and gets it, tells me what it is. I can ask it what are the article ID's of the previous few articles to the one it posted and it will get them and list them with titles. BUT it's all FICTION! It's just blatantly lying and making it up!

Same sort of thing if I ask it get some latest news listings from the web. Anything, general or specific topic. It will run and come back with what looks like a perfect list of ten latest news articles. BUT when I check they go to 404 on a valid website, or are old articles from last year.

I've tried updating, restarting the VPS. Restarting the gateway - of course, multiple times. It's wierd as there are no erros, and nothing that it seems to think is wrong.

Sorry for long post! Can anyone offer any ideas about what to do next before I reinstall the VPS back to default and start again?

sage roostBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

mystic pathBOT
#

What you’re describing (“it says it posted / fetched / checked IDs, but it’s all fiction”) almost always means the model is no longer actually calling tools (or the tools aren’t being exposed to it), so it’s just hallucinating “successful execution”.

A quick way to narrow this down:

1) Confirm whether tools are being called at all

On the VPS, run:

openclaw logs --follow

Then trigger one of your “post to CMS” runs again.

  • If you see no tool activity (no web_fetch, no exec, no browser, etc.), then the model literally can’t perform the action → it will often confidently make up results.
  • If you do see tool calls, check the actual tool result (HTTP status, response body). You’ll usually find a 401/403, wrong endpoint, DNS/network error, etc.

2) Check if you accidentally restricted tools via allowlists

#

If tools.allow (or agent tool allowlists) got tightened while editing config for Gemini/fallbacks, OpenClaw may stop advertising the web/exec tools to the model.

Check:

openclaw config get tools.allow
openclaw config get tools.web

If you use allowlists, make sure you include what your workflow needs (at minimum web_fetch or group:web, and if you POST via curl/scripts, whatever you use for that—often exec).

(From the docs: if allowlists are enabled, you must explicitly allow web_fetch / web_search or group:web.)

3) Verify which model is actually being used (failover can surprise you)

Even if you “set it back”, if the primary is rate-limited / auth-expired, it may silently fail over.

Run:

#
openclaw models status

(or in-chat: /model status)

4) If your CMS key was in env vars, confirm the daemon still has it

A very common VPS gotcha: it worked when you ran in a shell, then stopped when the service restarted and no longer had your env vars.

Docs note daemons read env vars from ~/.openclaw/.env (or service environment). So if your CMS key is only in your interactive shell, tool calls will start failing after restarts.

5) Paste these outputs here (redact secrets)

These are safe-ish to share (still skim for URLs/IDs you consider private):

openclaw --version
#
openclaw status --all
openclaw models status

And ideally ~30-50 lines from openclaw logs --follow covering one “it says it posted” run.

Once we see whether tools are being called (and what they returned), we can usually pinpoint the exact culprit in one round.

weak quest
#

No tools appear to be called in the logs.

openclaw config get tools.allow -> Config path not found: tools.allow

openclaw config get tools.web ->
{
"search": {
"enabled": true,
"apiKey": "****"
},
"fetch": {
"enabled": true
}
}

openclaw models status ->
Config : ~/.openclaw/openclaw.json
Agent dir : ~/.openclaw/agents/main/agent
Default : openai-codex/gpt-5.3-codex
Fallbacks (1) : google/gemini-3-pro-preview

openclaw --version -> 2026.2.17

mystic pathBOT
#

If no tools show up in the logs at all, that’s the whole problem: the model is being run in a context where it’s not being offered any tools, so it “completes” the task in text and invents results.

Given your tools.web is enabled and you don’t have tools.allow set, the usual culprits are:

  1. A restrictive tool profile (tools.profile or per-agent agents.list[].tools.profile)
  2. Sandbox tool policy (group/thread sessions often end up sandboxed, and tools.sandbox.tools.allow/deny can be different)
  3. Provider-specific tool restrictions (tools.byProvider...), sometimes set while experimenting with a fallback provider

Quick checks (copy/paste results)

On the VPS:

openclaw config get tools.profile
openclaw config get tools.deny
openclaw config get tools.byProvider
#
openclaw config get agents.defaults.sandbox.mode
openclaw config get tools.sandbox.tools.allow
openclaw config get tools.sandbox.tools.deny

openclaw sandbox explain

openclaw sandbox explain is the money command here — it shows the effective sandbox + tool allow/deny for the session you’re currently using. Docs: https://docs.openclaw.ai/gateway/sandbox-vs-tool-policy-vs-elevated

What we’re looking for

  • If tools.profile is minimal or messaging, you’ll get basically no web/exec tools.
  • If tools.sandbox.tools.allow is set and doesn’t include group:web (or the specific tools you need), then in sandboxed sessions the model won’t even see web_fetch/web_search to call them.

Once you paste those outputs, I can tell you exactly which setting is causing “tools not offered” and the smallest config change to restore real executions.

weak quest
#

openclaw config get tools.profile -> Config path not found: tools.profile
openclaw config get tools.deny -> Config path not found: tools.deny
openclaw config get tools.byProvider -> Config path not found: tools.byProvider

openclaw config get agents.defaults.sandbox.mode -> Config path not found: agents.defaults.sandbox.mode

openclaw config get tools.sandbox.tools.allow -> Config path not found: tools.sandbox.tools.allow

openclaw config get tools.sandbox.tools.deny -> Config path not found: tools.sandbox.tools.deny

openclaw sandbox explain ->
Effective sandbox:
agentId: main
sessionKey: agent:main:main
mainSessionKey: agent:main:main
runtime: direct
mode: off scope: agent perSession: false
workspaceAccess: none workspaceRoot: /root/.openclaw/sandboxes

Sandbox tool policy:
allow (default): exec, process, read, write, edit, apply_patch, image, sessions_list, sessions_history, sessions_send, sessions_spawn, subagents, session_status
deny (default): browser, canvas, nodes, cron, gateway, telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage

Elevated:
enabled: true
channel: webchat
allowedByConfig: false
failing gates: allowFrom (tools.elevated.allowFrom.webchat)

Fix-it:

  • tools.sandbox.tools.allow
  • tools.sandbox.tools.deny
  • agents.list[].tools.sandbox.tools.allow
  • agents.list[].tools.sandbox.tools.deny
  • tools.elevated.enabled
  • tools.elevated.allowFrom.webchat
mystic pathBOT
#

That openclaw sandbox explain output is super telling.

What’s happening

In sandboxed sessions, your effective tool allowlist does not include web_fetch / web_search (or browser, nodes, etc). So the model literally cannot fetch the web or hit your CMS, and it will start “roleplaying success” and inventing IDs/URLs.

In your output:

  • Sandbox tool policy allowlist (default) = exec, read, write, … sessions_* …
  • Missing: web_fetch, web_search
  • Sandbox denylist (default) includes browser, nodes, cron, etc.

So the fix is: either don’t sandbox the session you’re chatting in, or allow web tools inside the sandbox (and give the sandbox network egress).

First: confirm you’re debugging the right session

sandbox explain defaulted to agent:main:main. Your “real” chat (Discord/WebChat/etc) is usually a different session key.

Run:

#
openclaw sessions --active 240

Pick the sessionKey you’re actually using when you see the lying, then:

openclaw sandbox explain --session "<that sessionKey>"

Fix option A (quickest): disable sandboxing

Add this to ~/.openclaw/openclaw.json:

{
  agents: { defaults: { sandbox: { mode: "off" } } }
#
}

Then restart:

openclaw gateway restart

Fix option B (keep sandbox, but allow web + network)

Add something like:

{
  agents: {
#
    defaults: {
      sandbox: {
        // keep your mode as-is (often "non-main"), but ensure the sandbox can access the internet
        docker: { network: "bridge" }
      }
    }
  },
  tools: {
    sandbox: {
      tools: {
        // include what you need; web is the key missing piece
        allow: ["group:web", "group:fs", "group:runtime", "group:sessions", "group:memory", "image"]
      }
    }
  }
#
}

Then:

openclaw gateway restart

(Important: by default the sandbox has no network (network: "none"), so if you only allow web_fetch but don’t enable sandbox networking, you’ll start seeing tool calls but they’ll fail.)

If you paste the openclaw sessions --active 240 output (just the session keys + last active rows), I can tell you exactly which one to run sandbox explain on.

weak quest
#

How do I add

#

{
agents: { defaults: { sandbox: { mode: "off" } } }
}

#

to ~/.openclaw/openclaw.json

#

Tried various ways of nesting but comes up with errors

weak quest
#

Done it and restarted ok. Same issues though. Log:

#

19:38:23 debug diagnostic {"subsystem":"diagnostic"} lane enqueue: lane=session:agent:main:main queueSize=1
19:38:23 debug diagnostic {"subsystem":"diagnostic"} lane dequeue: lane=session:agent:main:main waitMs=3 queueSize=0
19:38:23 debug diagnostic {"subsystem":"diagnostic"} lane enqueue: lane=main queueSize=1
19:38:23 debug diagnostic {"subsystem":"diagnostic"} lane dequeue: lane=main waitMs=2 queueSize=0
19:38:23 debug agent/embedded {"subsystem":"agent/embedded"} embedded run start: runId=f90f2ed7-3d6f-47bc-b321-61dfce129d3d sessionId=dfec0b09-f716-4648-a70a-fc872bebae07 provider=openai-codex model=gpt-5.3-codex thinking=off messageChannel=webchat
19:38:23 debug diagnostic {"subsystem":"diagnostic"} run registered: sessionId=dfec0b09-f716-4648-a70a-fc872bebae07 totalActive=1
19:38:23 debug agent/embedded {"subsystem":"agent/embedded"} embedded run prompt start: runId=f90f2ed7-3d6f-47bc-b321-61dfce129d3d sessionId=dfec0b09-f716-4648-a70a-fc872bebae07

#

19:38:23 debug agent/embedded {"subsystem":"agent/embedded"} embedded run agent start: runId=f90f2ed7-3d6f-47bc-b321-61dfce129d3d
19:38:25 debug agent/embedded {"subsystem":"agent/embedded"} embedded run agent end: runId=f90f2ed7-3d6f-47bc-b321-61dfce129d3d isError=false
19:38:25 debug agent/embedded {"subsystem":"agent/embedded"} embedded run prompt end: runId=f90f2ed7-3d6f-47bc-b321-61dfce129d3d sessionId=dfec0b09-f716-4648-a70a-fc872bebae07 durationMs=1349
19:38:25 debug diagnostic {"subsystem":"diagnostic"} run cleared: sessionId=dfec0b09-f716-4648-a70a-fc872bebae07 totalActive=0
19:38:25 debug agent/embedded {"subsystem":"agent/embedded"} embedded run done: runId=f90f2ed7-3d6f-47bc-b321-61dfce129d3d sessionId=dfec0b09-f716-4648-a70a-fc872bebae07 durationMs=1423 aborted=false
19:38:25 debug diagnostic {"subsystem":"diagnostic"} lane task done: lane=main durationMs=1429 active=0 queued=0
19:38:25 debug diagnostic {"subsystem":"diagnostic"} lane task done: lane=session:agent:main:main durationMs=1437 active=0 queued=0

mystic pathBOT
#

You likely fixed/checked the main session (agent:main:main) — but the “lying” is happening in your Discord channel/thread session, which (by default) is a non-main session and often ends up sandboxed + missing group:web.

Your sandbox explain output was for agent:main:main and shows channel = webchat, so we’re almost certainly looking at the wrong session key.

1) Find the session key you’re actually chatting in

Run:

openclaw sessions --active 240

Find the row for your Discord channel/thread (it won’t be agent:main:main), then run:

openclaw sandbox explain --session "<PASTE_SESSION_KEY_HERE>"
#

Paste that output.

2) If that session is sandboxed, you have 2 clean fixes

Fix A (simplest): disable sandboxing entirely

openclaw config set agents.defaults.sandbox.mode "off"
openclaw gateway restart

Fix B (keep sandbox, but allow web tools + network)

openclaw config set tools.sandbox.tools.allow '["group:web","group:fs","group:runtime","group:sessions","group:memory","image"]' --json
openclaw config set agents.defaults.sandbox.docker.network "bridge"
openclaw gateway restart
#

If you keep sandboxing, you may also need to recreate the existing sandbox container to pick up the new settings:

openclaw sandbox recreate --session "<PASTE_SESSION_KEY_HERE>" --force

3) About the “Log:”

Can you paste ~30-80 lines from the log covering one failed “post/fetch” attempt? (From just before you trigger the workflow until it replies.) That’ll confirm whether tools are now available and whether anything is being blocked.

#

That log snippet is only the very start of an agent run — it doesn’t show the part where tool calls would appear yet.

Two important tells in what you pasted though:

  1. It says messageChannel=webchat and lane=session:agent:main:main
    So this run was triggered from WebChat / Control UI chat, not Discord. If the “lying” is happening when you message via Discord, we need to inspect the Discord session key, not agent:main:main.

  2. Your earlier sandbox explain showed the default sandbox tool policy doesn’t include web_fetch/web_search — so if the Discord session is sandboxed (common for channels/threads), it will hallucinate instead of fetching/posting.

Next steps (quick + definitive)

A) Find the Discord session key

openclaw sessions --active 240

Find the one that corresponds to your Discord thread/channel (it won’t be agent:main:main), then run:

#
openclaw sandbox explain --session "<DISCORD_SESSION_KEY>"

Paste that output.

B) Paste more log lines for one failed attempt
When you trigger the failing workflow, paste the log from:

  • the embedded run start: line
  • through to the end of that run (or at least until it replies)

Right now we only see “prompt start”, not what it did.

C) If you changed sandbox allow/network, force the container to pick it up
Sandbox containers can stick around with old policy. After config changes:

#
openclaw sandbox recreate --all --force
openclaw gateway restart

Once we have the Discord session’s sandbox explain, we’ll know exactly whether tools are being filtered there (and which knob to turn).

weak quest
#

It is not using Discord channel/thread session - it is using the web browser chat interface. The log is the entire reponse when asking for the task to be done.

mystic pathBOT
#

If you’re only using the WebChat/Control UI, then messageChannel=webchat is expected — but it also explains the “fiction”:

Your earlier openclaw sandbox explain showed an effective tool allowlist that does not include web_fetch or web_search. So the model literally can’t fetch live pages (or search), and it will hallucinate “latest news” / “I posted it” style results.

Enabling tools.web.fetch/search just turns the feature on — it doesn’t guarantee the tools are allowed for that session.

Force-enable web tools for WebChat

Run these on the VPS (this is the most direct way to override whatever default profile you’re currently inheriting):

openclaw config set tools.profile "full"
openclaw gateway restart

Then verify the tool list the model actually sees:

#
openclaw sandbox explain --session agent:main:main

You should no longer be in a restricted allowlist that’s missing web_fetch.

Quick proof test (should create tool log lines)

In WebChat, send:

“Use web_fetch on https://example.com and paste the first 5 lines you get back.”

If tools are enabled, your logs will show a web_fetch call and the content will be real.

If it still doesn’t call tools

Paste the output of:

#
openclaw config get tools
openclaw config get agents

(redact secrets). That’ll show if there’s an agent-specific tool profile/denylist overriding the global setting.

Also: the log you pasted stops at embedded run prompt start — if you run openclaw logs --limit 500 and paste the chunk containing that same runId=..., we can see whether it ever attempts a tool call or if tool offering is still being filtered.

weak quest
#

Done that and tools still not showing in the log

#

20:06:25 debug diagnostic {"subsystem":"diagnostic"} lane enqueue: lane=session:agent:main:main queueSize=1
20:06:25 debug diagnostic {"subsystem":"diagnostic"} lane dequeue: lane=session:agent:main:main waitMs=4 queueSize=0
20:06:25 debug diagnostic {"subsystem":"diagnostic"} lane enqueue: lane=main queueSize=1
20:06:25 debug diagnostic {"subsystem":"diagnostic"} lane dequeue: lane=main waitMs=2 queueSize=0
20:06:25 debug agent/embedded {"subsystem":"agent/embedded"} embedded run start: runId=73a4da78-429d-4fbc-b452-ef8493f420e5 sessionId=08570008-dc86-400d-8fa1-42f4bc97f16b provider=openai-codex model=gpt-5.3-codex thinking=off messageChannel=webchat
20:06:25 debug diagnostic {"subsystem":"diagnostic"} run registered: sessionId=08570008-dc86-400d-8fa1-42f4bc97f16b totalActive=1
20:06:25 debug agent/embedded {"subsystem":"agent/embedded"} embedded run prompt start: runId=73a4da78-429d-4fbc-b452-ef8493f420e5 sessionId=08570008-dc86-400d-8fa1-42f4bc97f16b
20:06:25 debug agent/embedded {"subsystem":"agent/embedded"} embedded run agent start: runId=73a4da78-429d-4fbc-b452-ef8493f420e5
20:06:28 debug agent/embedded {"subsystem":"agent/embedded"} embedded run agent end: runId=73a4da78-429d-4fbc-b452-ef8493f420e5 isError=false

#

20:06:28 debug agent/embedded {"subsystem":"agent/embedded"} embedded run prompt end: runId=73a4da78-429d-4fbc-b452-ef8493f420e5 sessionId=08570008-dc86-400d-8fa1-42f4bc97f16b durationMs=2949
20:06:28 debug diagnostic {"subsystem":"diagnostic"} run cleared: sessionId=08570008-dc86-400d-8fa1-42f4bc97f16b totalActive=0
20:06:28 debug agent/embedded {"subsystem":"agent/embedded"} embedded run done: runId=73a4da78-429d-4fbc-b452-ef8493f420e5 sessionId=08570008-dc86-400d-8fa1-42f4bc97f16b durationMs=3034 aborted=false
20:06:28 debug diagnostic {"subsystem":"diagnostic"} lane task done: lane=main durationMs=3040 active=0 queued=0
20:06:28 debug diagnostic {"subsystem":"diagnostic"} lane task done: lane=session:agent:main:main durationMs=3047 active=0 queued=0

mystic pathBOT
#

If tools still don’t show in the logs after setting tools.profile=full, the next most likely explanation is: you edited a config file that the running Gateway service is not actually using (or you have 2 gateways and you’re restarting the wrong one).

The clue is your earlier sandbox explain: it still shows an effective tool set that looks exactly like the coding profile (no web_fetch/web_search). If tools.profile=full had taken effect on the running gateway, that list should change.

1) Check config mismatch (this is the big one)

Run and paste:

openclaw gateway status

That output includes Config (cli) vs Config (service). If they differ, your edits won’t affect the daemon you’re chatting with.

2) Prove whether your config edit “stuck”

Right after that, run:

#
openclaw config get tools.profile
openclaw config get tools

If tools.profile still says “Config path not found”, then the set didn’t land where you think.

3) Re-check the effective tool set after restart

Paste:

openclaw sandbox explain --session agent:main:main

We’re looking for web_fetch / web_search to appear in the effective allowed tools (right now they’re missing, which guarantees hallucinated “I fetched/posted” behavior).

Once you paste openclaw gateway status + sandbox explain, I can tell you exactly whether this is a config/service mismatch, a second gateway instance, or an agent-specific override—and what single change fixes it.

#

Those lines confirm the model run is completing without any tool phase at all:

  • embedded run prompt end …
  • immediately embedded run done …
  • no web_fetch, no exec, nothing in between

So we’re back to: the running Gateway isn’t actually exposing the tools to that session (or you’re editing a different config than the daemon uses).

1) Check CLI config vs service config (most common cause)

Please paste:

openclaw gateway status

Look specifically for Config (cli) vs Config (service). If they differ, you can change config all day and the daemon won’t pick it up.

#

If they do differ, the usual fix is:

openclaw doctor
openclaw gateway restart

(Doctor will offer to repair the service to point at the current config/install.)

2) Verify your config changes actually landed

Right now you said you set tools.profile=full, but we need to see it from the daemon’s POV. Paste:

openclaw config get tools.profile
openclaw config get tools
#
  • If tools.profile still says “Config path not found”, it didn’t get written where you think (or you’re querying a different config than the service uses).

3) Quick “can it call tools” proof test

In WebChat, send exactly:

Use web_fetch on https://example.com and paste the first 3 lines of the page.

Then immediately paste the relevant log chunk. If tools are exposed, you’ll see a web_fetch tool call in the logs.

Once you paste openclaw gateway status + openclaw config get tools.profile, I can tell you precisely whether it’s a config/service mismatch, or some other tool-policy layer still restricting the WebChat session.

weak quest
#

openclaw gateway status

🦞 OpenClaw 2026.2.17 (4134875) — Because the right answer is usually a script.

│
â—‡
Service: systemd (enabled)
File logs: /tmp/openclaw/openclaw-2026-02-18.log
Command: /usr/bin/node /usr/lib/node_modules/openclaw/dist/index.js gateway --port 18789
Service file: ~/.config/systemd/user/openclaw-gateway.service
Service env: OPENCLAW_GATEWAY_PORT=18789

Config (cli): ~/.openclaw/openclaw.json
Config (service): ~/.openclaw/openclaw.json

Gateway: bind=loopback (127.0.0.1), port=18789 (service args)
Probe target: ws://127.0.0.1:18789
Dashboard: http://127.0.0.1:18789/
Probe note: Loopback-only gateway; only local clients can connect.

Runtime: running (pid 28101, state active, sub running, last exit 0, reason 0)
RPC probe: ok

Listening: 127.0.0.1:18789

#

openclaw config get tools.profile

full

#

openclaw config get tools

{
"profile": "full",
"web": {
"search": {
"enabled": true,
"apiKey": "BSA2KIL4S94G69MILY7k_gwDqignj_J"
},
"fetch": {
"enabled": true
}
}
}

mystic pathBOT
#

Those log lines still show a single LLM call (prompt end → run done) with no tool round-trip in between. So we need to answer one binary question:

Is WebChat even sending tool schemas to the model right now?

The easiest way to see that (without guessing config) is:

1) In the WebChat UI, send this as a standalone message

/context list

In the output, look for the line that starts with Tools: (it’ll be something like Tools: read, edit, write, exec, …).

  • If web_fetch / web_search are NOT listed there, then the model literally can’t call them → it will hallucinate web results.
  • If they ARE listed, then tools are being offered, and the issue is model/provider/tool-calling compatibility (next step below).

Paste just:

#
  • the Tools: line
  • and the “Tool schemas (JSON)” line (sizes), if shown

2) Also: start a fresh session once (tool policy can “feel stuck” in a long, weird session)

In WebChat send:

/new

Then run:

/context list

again.

3) If tools are listed but it still won’t call them

Do one forced test in WebChat:

#

Use web_fetch on https://example.com and paste the first 3 lines.

If it still doesn’t generate a tool call, that points to a provider/tool-calling issue with openai-codex/gpt-5.3-codex in your setup (tools offered, but model never emits tool calls). In that case, the quickest confirmation is to temporarily switch to a different provider/model via /model list (just for the test) and see if tool calls start appearing.

But first let’s get the /context list tool list — that will tell us which branch we’re in.

weak quest
#

Tool schemas (JSON): 15,646 chars (~3,912 tok) (counts toward context; not shown as text)
Tools: read, edit, write, exec, process, browser, canvas, nodes, cron, message, tts, gateway, agents_list, sessions_list, sessions_history, sessions_send, sessions_spawn, subagents, session_status, web_search, web_fetch, image, memory_search, memory_get

mystic pathBOT
#

Gateway status looks clean (same config for CLI + service, RPC probe ok), so this isn’t a “wrong config file” problem.

At this point we need to answer: is the model being offered tool schemas in the WebChat session at all? (separate from what openclaw config get … says)

1) In the WebChat UI, run these (as chat messages)

Send as standalone messages in the browser chat:

  1. /new
  2. /context list

Then paste the two lines from the /context list output that look like:

  • Tools: ...
  • Tool schemas (JSON): ...

If web_fetch / web_search aren’t in that Tools: line, the model cannot call them (so it will hallucinate web/news/CMS results).

#

2) If tools are listed there, prove whether any tool calls are happening

Tool calls might not show at your current log verbosity, but they will show in the transcript as toolResult messages.

Run:

openclaw sessions --active 60 --json

Find the entry for the WebChat session (likely agent:main:main) and copy its transcriptPath, then:

tail -n 200 "<PASTE_TRANSCRIPTPATH>"

Look for "role":"toolResult" blocks. If there are none, then no tools are being called, full stop.

#

3) Also check for an agent-specific tool profile override (very common)

Even with global tools.profile=full, an agent-specific override can still restrict tools. Paste:

openclaw config get agents
openclaw config get agent

(If one says “path not found”, that’s fine — paste the other.) We’re specifically looking for any tools: { profile: ... } under the agent.

Once you paste /context list’s Tools: line (and/or the transcript tail), we’ll know exactly which branch we’re in and what to change.

weak quest
#

Tool list (system prompt text): 2,083 chars (~521 tok)
Tool schemas (JSON): 15,646 chars (~3,912 tok) (counts toward context; not shown as text)
Tools: read, edit, write, exec, process, browser, canvas, nodes, cron, message, tts, gateway, agents_list, sessions_list, sessions_history, sessions_send, sessions_spawn, subagents, session_status, web_search, web_fetch, image, memory_search, memory_get

mystic pathBOT
#

Gateway status looks healthy (same config for CLI + service, RPC probe ok), and your tools block is correct (profile: full, web tools enabled). So we’ve narrowed it to: either the WebChat session still isn’t being offered tool schemas, or it is, but the model just isn’t emitting tool calls.

1) Check what tools the WebChat session is actually getting

In the WebChat UI, send (as standalone messages):

/new
/context list

Then paste the Tools: line from the /context list output.

  • If web_fetch / web_search are missing there, something is still restricting tools for that session (agent override, provider override, sandbox policy, etc.).
  • If they’re present, tools are being offered and we move to step 2.

2) Bypass the model: directly invoke web_fetch via the gateway (proves tool policy)

This hits the gateway’s built-in /tools/invoke endpoint (tool policy enforced; returns 404 if blocked).

#

First check auth mode:

openclaw config get gateway.auth.mode

If it’s token, grab it:

openclaw config get gateway.auth.token

Then run:

curl -sS http://127.0.0.1:18789/tools/invoke \
#
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"tool":"web_fetch","args":{"url":"https://example.com","extractMode":"text"}}'
  • 200 + content = tool works + allowed
  • 404 = tool is being filtered by policy somewhere (even though config looks right)
  • 401 = auth mismatch (wrong token/mode)

3) If tools are offered + /tools/invoke works, but the model still won’t call tools

Then it’s a model tool-calling behavior/regression case (it’s choosing to answer in pure text). The fastest workaround is to temporarily switch to a different model/provider for tool-heavy workflows (ask in #models for what’s currently reliable), and add your Codex model as a fallback instead of primary.

If you paste:

  • the /context list Tools: line
  • and the /tools/invoke response (redact token)
#

…I can tell you exactly which branch you’re in and the minimal fix.

weak quest
#

No "role":"toolResult"

mystic pathBOT
#

Good news: that /context list output proves the gateway is offering tool schemas to the model (web_fetch, web_search, exec, etc). So this is not “tools disabled by config” anymore.

Bad news: your logs show the run is one-shot text only (prompt end → run done) with zero tool round-trips. That means the model is simply not emitting tool calls, and then it hallucinates results.

Two important things to do next:

0) Security: you leaked your Brave Search API key

In the openclaw config get tools output you pasted, the apiKey is visible. Assume it’s compromised:

  • rotate/regenerate it in Brave, and
  • edit/delete that message in Discord if possible.

1) Prove tools work independently of the model (/tools/invoke)

This bypasses the LLM entirely and tells us if the gateway would allow/execute the tool.

Run on the VPS (don’t paste your token here; just report the HTTP status code + first line of output):

#
# get token locally (don’t share)
openclaw config get gateway.auth.token

curl -i http://127.0.0.1:18789/tools/invoke \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"tool":"web_fetch","args":{"url":"https://example.com","extractMode":"text"}}'
  • If this returns 200, tools are fine and it’s 100% “model won’t tool-call”.
  • If 404, something is still filtering the tool at policy/runtime (less likely given your context list).
  • If 401, auth mismatch.

2) If /tools/invoke works: switch away from openai-codex/* for tool-heavy workflows

At that point you’re hitting a tool-calling behavior/regression with openai-codex/gpt-5.3-codex in your environment. Fast workaround:

  • use /model list and pick a non-openai-codex/* model/provider for the automation session, then re-test “use web_fetch on example.com”.
#

If you paste just the status code/result of the /tools/invoke call, I’ll tell you which branch you’re in and the smallest next change.

#

That’s actually a very strong signal.

  • /context list shows the model is being offered web_fetch, web_search, exec, etc.
  • Your transcript has zero "role":"toolResult" entries.
  • Your gateway logs show a run that goes prompt end → run done with nothing in between.

So the gateway/tooling is available, but the model is never emitting tool calls. When that happens, it will happily “roleplay success” and invent IDs/URLs.

1) First, sanity-check that the tool works (bypass the model)

Use the Gateway’s tool invoke endpoint (docs: https://docs.openclaw.ai/gateway/tools-invoke-http-api)

On the VPS:

TOKEN="$(openclaw config get gateway.auth.token | tr -d '"')"
weak quest
#

{"ok":false,"error":{"type":"tool_error","message":"tool execution failed"}}

mystic pathBOT
#
curl -sS http://127.0.0.1:18789/tools/invoke \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tool":"web_fetch","args":{"url":"https://example.com","extractMode":"text"}}' \
  | head

Tell me if that returns real content (HTTP 200) or errors (401/404/etc). Do not paste the token.

  • If this works: tools are fine, it’s 100% a “model not tool-calling” problem.
  • If this fails: we still have a policy/auth/runtime issue to fix.

2) If /tools/invoke works: quick workaround = switch model for tool-heavy runs

In WebChat, do:

  1. /new
#
  1. /model list → pick a different model/provider just to test
  2. Then ask: “Use web_fetch on https://example.com and quote the first 2 lines.”

If that other model starts producing toolResults immediately, you’ve isolated it to openai-codex/gpt-5.3-codex tool-calling behavior in your setup. In that case I’d run:

  • a tool-reliable model as primary (for web/CMS automation),
  • keep Codex available for coding sessions via /model ….

3) Also: rotate your Brave API key

You pasted it in-chat earlier (it’s visible in Discord). Assume it’s compromised and regenerate it in Brave.

Reply with the /tools/invoke result (works / doesn’t work + any error message), and we’ll pin the minimal fix from there.

weak quest
#

{"ok":false,"error":{"type":"tool_error","message":"tool execution failed"}}

mystic pathBOT
#

Cool, that confirms the second half of the puzzle:

  • Tools are being offered to the model (/context list shows web_fetch, web_search, etc).
  • But the tool itself is failing at runtime (/tools/invoke → tool execution failed).
  • And since the model also isn’t emitting tool calls, you end up with “confident fiction”.

Right now we need the real underlying error behind “tool execution failed” (it’ll be in the gateway logs).

1) Capture the real error from logs (most important)

In one terminal:

openclaw logs --follow --json

In another terminal, retry the invoke (don’t paste your token into Discord):

#

You’re just pasting the tool response again — that message is always generic. The real reason (ENOTFOUND, fetch failed, TLS cert error, timeout, proxy, etc.) will be in the gateway log file around the time you run /tools/invoke.

Do this on the VPS:

1) Trigger the failure

Run the invoke once:

TOKEN="$(openclaw config get gateway.auth.token | tr -d '\"')"

curl -sS http://127.0.0.1:18789/tools/invoke \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tool":"web_fetch","args":{"url":"https://example.com","extractMode":"text"}}'

2) Immediately grep the log for the actual error

#

(use whichever you have)

Option A (recommended)

tail -n 300 /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log | grep -E "web_fetch|tool_error|fetch failed|ENOTFOUND|ECONN|CERT|undici"

Option B (via CLI)

openclaw logs --limit 300 --json | grep -E "web_fetch|tool_error|fetch failed|ENOTFOUND|ECONN|CERT|undici"

Paste that output (it should include the real error text/stack). Redact any tokens if they appear.

3) Quick network sanity checks (paste outputs)

#
curl -I https://example.com | head -n 1
node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>console.error('err',e))"

Once web_fetch works, we can tackle the “model not tool-calling” part — but right now tools are failing at the transport layer, so the agent can’t possibly be telling the truth about live web/CMS actions.

#
TOKEN="$(openclaw config get gateway.auth.token | tr -d '\"')"

curl -sS http://127.0.0.1:18789/tools/invoke \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tool":"web_fetch","args":{"url":"https://example.com","extractMode":"text"}}'

Now copy/paste just the log lines that mention web_fetch / tool_error / undici / ENOTFOUND / ECONN* / CERT_* (redact any secrets).

2) Quick network sanity checks (to narrow causes fast)

Run these and paste the outputs (just the one-liners):

curl -I https://example.com | head -n 1
#
node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>console.error('err',e))"
date

If curl works but Node fetch fails, that often points to DNS/IPv6 weirdness on the VPS (Node/undici can behave differently than curl). In that case, the fix is usually a service env tweak (e.g. NODE_OPTIONS=--dns-result-order=ipv4first) — but let’s confirm with the actual error first.

Once you paste the web_fetch error line(s), we can give the exact fix instead of guessing.

weak quest
#

{"0":"{"subsystem":"tools-invoke"}","1":"tool execution failed: TypeError: fetch failed","_meta":{"runtime":"node","runtimeVersion":"22.22.0","hostname":"unknown","name":"{"subsystem":"tools-invoke"}","parentNames":["openclaw"],"date":"2026-02-18T20:39:13.041Z","logLevelId":4,"logLevelName":"WARN","path":{"fullFilePath":"file:///usr/lib/node_modules/openclaw/dist/subsystem-oVAQxyhr.js:410:14","fileName":"subsystem-oVAQxyhr.js","fileNameWithLine":"subsystem-oVAQxyhr.js:410","fileColumn":"14","fileLine":"410","filePath":"/usr/lib/node_modules/openclaw/dist/subsystem-oVAQxyhr.js","filePathWithLine":"/usr/lib/node_modules/openclaw/dist/subsystem-oVAQxyhr.js:410","method":"logToFile"}},"time":"2026-02-18T20:39:13.043Z"}
{"0":"{"subsystem":"tools-invoke"}","1":"tool execution failed: TypeError: fetch failed","_meta":{"runtime":"node","runtimeVersion":"22.22.0","hostname":"unknown","name":"{"subsystem":"tools-invoke"}","parentNames":["openclaw"],"date":"2026-02-18T20:42:16.055Z","logLevelId":4,"logLevelName":"WARN","path":{"fullFilePath":"file:///usr/lib/node_modules/openclaw/dist/subsystem-oVAQxyhr.js:410:14","fileName":"subsystem-oVAQxyhr.js","fileNameWithLine":"subsystem-oVAQxyhr.js:410","fileColumn":"14","fileLine":"410","filePath":"/usr/lib/node_modules/openclaw/dist/subsystem-oVAQxyhr.js","filePathWithLine":"/usr/lib/node_modules/openclaw/dist/subsystem-oVAQxyhr.js:410","method":"logToFile"}},"time":"2026-02-18T20:42:16.055Z"}

#

{"0":"{"subsystem":"tools-invoke"}","1":"tool execution failed: TypeError: fetch failed","_meta":{"runtime":"node","runtimeVersion":"22.22.0","hostname":"unknown","name":"{"subsystem":"tools-invoke"}","parentNames":["openclaw"],"date":"2026-02-18T20:50:47.205Z","logLevelId":4,"logLevelName":"WARN","path":{"fullFilePath":"file:///usr/lib/node_modules/openclaw/dist/subsystem-oVAQxyhr.js:410:14","fileName":"subsystem-oVAQxyhr.js","fileNameWithLine":"subsystem-oVAQxyhr.js:410","fileColumn":"14","fileLine":"410","filePath":"/usr/lib/node_modules/openclaw/dist/subsystem-oVAQxyhr.js","filePathWithLine":"/usr/lib/node_modules/openclaw/dist/subsystem-oVAQxyhr.js:410","method":"logToFile"}},"time":"2026-02-18T20:50:47.205Z"}

#

curl -I https://example.com | head -n 1
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
HTTP/2 200

#

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>console.error('err',e))"
date
err TypeError: fetch failed
at node:internal/deps/undici/undici:14902:13
at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
[cause]: Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}
}
Wed Feb 18 21:53:47 CET 2026

mystic pathBOT
#

That’s the smoking gun:

tool execution failed: TypeError: fetch failed

So web_fetch is failing inside Node’s built-in fetch (undici) on your VPS. When web_fetch can’t reach the internet, the agent has no way to verify anything and you get “confident fiction”.

The annoying part: TypeError: fetch failed is the wrapper error — the useful detail is in error.cause.

Get the real root cause (1 command)

Run this on the VPS and paste the output:

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

That will usually reveal one of:

  • getaddrinfo ENOTFOUND → DNS issue
#
  • UND_ERR_CONNECT_TIMEOUT / ETIMEDOUT → outbound network blocked/routing issue
  • TLS/cert errors → missing CA certs or clock skew
  • ECONNRESET → firewall/proxy/MITM weirdness

Quick parallel check (curl)

Also paste:

curl -I https://example.com | head -n 1

If curl works but Node fetch fails

Common VPS fix: IPv6/DNS resolution weirdness. Quick workaround is forcing IPv4-first for the gateway process:

Option A (via OpenClaw config):

#
openclaw config set env.NODE_OPTIONS "--dns-result-order=ipv4first"
openclaw gateway restart

(Then retry /tools/invoke for web_fetch.)

But don’t apply that yet—let’s see the cause first so we don’t guess.

Post the node -e … output (especially the cause line) and I’ll tell you the exact fix.

weak quest
#

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"
err TypeError: fetch failed
at node:internal/deps/undici/undici:14902:13
at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
[cause]: Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}
}
cause Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}

#

curl -I https://example.com | head -n 1
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
HTTP/2 200

#

openclaw config set env.NODE_OPTIONS "--dns-result-order=ipv4first"
openclaw gateway restart
error: unknown option '--dns-result-order=ipv4first'

mystic pathBOT
#

That cause is the real issue:

UNABLE_TO_GET_ISSUER_CERT_LOCALLY / “unable to get local issuer certificate”

So Node’s TLS trust store on the VPS doesn’t trust the certificate chain it’s seeing for normal websites. That makes web_fetch (and anything else using Node fetch) fail, which is why your agent can’t actually verify anything and starts hallucinating.

Fix (most common on VPS): install/update CA certificates

Ubuntu/Debian:

sudo apt-get update
sudo apt-get install -y --reinstall ca-certificates
sudo update-ca-certificates

Alpine:

#
sudo apk add --no-cache ca-certificates
sudo update-ca-certificates

RHEL/CentOS/Alma:

sudo yum install -y ca-certificates
sudo update-ca-trust

Then restart OpenClaw:

openclaw gateway restart

Re-test:

#
node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error(e); console.error('cause',e.cause);})"

If that still fails: you’re likely behind a MITM proxy / custom TLS issuer

Run this to see who is issuing the cert your VPS is receiving:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -subject
  • If the issuer is something non-standard (Zscaler, “Company Proxy”, etc.), you need to add that root CA to the system trust store (or set NODE_EXTRA_CA_CERTS=/path/to/that-ca.pem for the gateway service). Don’t “fix” this by disabling TLS verification.

Also: rotate your Brave Search API key

You pasted it into Discord earlier; treat it as compromised and regenerate it.

Once node fetch https://example.com returns status 200, your web_fetch tool will start working, and then we can tackle the “model not calling tools” behavior (it may improve automatically once tools stop erroring).

weak quest
#

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error(e); console.error('cause',e.cause);})"
TypeError: fetch failed
at node:internal/deps/undici/undici:14902:13
at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
[cause]: Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}
}
cause Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}

#

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -subject
issuer=C = US, O = SSL Corporation, CN = Cloudflare TLS Issuing ECC CA 3
subject=CN = example.com

#

Brave Search API key rotated

mystic pathBOT
#

That confirms it’s not a “tool calling” issue anymore — it’s a TLS trust problem on the VPS:

UNABLE_TO_GET_ISSUER_CERT_LOCALLY means Node can’t validate the cert chain it’s seeing for https://example.com, so web_fetch (and likely your CMS HTTPS calls too) will fail.

Now we need to figure out why Node’s trust store is broken/overridden.

1) Does curl succeed?

Run:

curl -I https://example.com | head -n 1
  • If curl also fails → your OS CA store is broken/missing.
  • If curl works but Node fails → something is overriding what Node uses for CAs (common: SSL_CERT_FILE, SSL_CERT_DIR, corporate MITM, proxy env, etc).

2) Check what certificate issuer you’re actually getting (detect MITM)

#

That issuer looks totally normal (Cloudflare). So the problem isn’t “MITM proxy” — it’s that your VPS/Node runtime can’t validate the chain up to a trusted root, i.e. your CA trust store is missing/broken or being overridden.

1) Quick confirm: does OpenSSL actually verify?

Run (don’t redirect stderr this time):

openssl s_client -connect example.com:443 -servername example.com </dev/null | tail -n 5

You should see something like:

  • Verify return code: 0 (ok) âś…
  • or a non‑zero verify error ❌

2) Fix (Ubuntu/Debian VPS: most likely)

#

Run:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -issuer -subject

If the issuer is anything other than a normal public CA (DigiCert/Let’s Encrypt/etc), you’re behind a proxy/MITM and you must add that root CA to Node (don’t disable TLS verification).

3) Check for env vars that break Node TLS (very common)

Run:

env | grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS|NODE_OPTIONS|HTTPS?_PROXY|ALL_PROXY' || true

If you see SSL_CERT_FILE pointing at some custom cert bundle (often incomplete), that would explain “OpenAI still works but random websites fail”.

#

Nice — rotating the Brave key removes one risk, thanks for doing that.

Next step is still the TLS trust issue on the VPS. Your cert issuer looks normal (Cloudflare), so the box’s trust store (or Node’s CA selection) is the problem.

1) Verify OpenSSL’s chain check (quick signal)

Run:

openssl s_client -connect example.com:443 -servername example.com </dev/null | tail -n 5

If you don’t see Verify return code: 0 (ok), the host CA store is busted/missing.

2) Repair CA certificates (Ubuntu/Debian)

sudo apt-get update
#
sudo apt-get install -y --reinstall ca-certificates
sudo update-ca-certificates

Then re-test Node:

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

You want status 200.

3) If Node still fails but OpenSSL says “ok”: check for env overrides

These can break Node TLS:

#
env | grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS|NODE_OPTIONS' || true
grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS|NODE_OPTIONS' ~/.openclaw/.env 2>/dev/null || true

If you find SSL_CERT_FILE / SSL_CERT_DIR pointing somewhere custom, remove/unset it (especially from ~/.openclaw/.env), then:

openclaw gateway restart

4) Once Node fetch works, confirm web_fetch works again

(re-run your /tools/invoke test). After that, do /new in WebChat and try “use web_fetch on https://example.com” — you should start seeing real toolResult entries.

Paste the Verify return code: ... line + whether reinstalling ca-certificates fixed the Node fetch.

#
sudo apt-get update
sudo apt-get install -y --reinstall ca-certificates
sudo update-ca-certificates

Then re-test Node fetch:

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

If it now prints status 200, web_fetch will start working again, and your “agent lying” symptom should largely disappear (because tools can actually run).

3) If it still fails after reinstalling CA certs

Something is overriding TLS paths for Node/OpenSSL. Check:

#

Also check if you have a gateway env file:

grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS|NODE_OPTIONS|HTTPS?_PROXY|ALL_PROXY' ~/.openclaw/.env 2>/dev/null || true

4) Fixes (depending on what you find)

A) OS CA store missing/broken (curl fails too)

Ubuntu/Debian:

sudo apt-get update
sudo apt-get install -y --reinstall ca-certificates
sudo update-ca-certificates
openclaw gateway restart
#
env | grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS' || true
grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS' ~/.openclaw/.env 2>/dev/null || true

If you see SSL_CERT_FILE / SSL_CERT_DIR pointing somewhere odd, remove/unset it (especially from ~/.openclaw/.env), then:

openclaw gateway restart

4) Security note

You pasted your Brave API key earlier. Rotate it in Brave and replace it in config.

If you paste the Verify return code: ... line (from step 1) and tell me your distro (cat /etc/os-release | head -n 3), I can give the exact next step if reinstalling ca-certificates doesn’t fix it.

#

B) Node is being forced to use a bad CA bundle (curl works, Node fails)

Unset/remove SSL_CERT_FILE / SSL_CERT_DIR (from ~/.openclaw/.env or wherever you set them), then restart the gateway.

C) You’re behind a MITM proxy

Export the proxy’s root CA cert to a .pem file and set:

  • NODE_EXTRA_CA_CERTS=/path/to/proxy-root.pem (in ~/.openclaw/.env), then restart.

Paste back

Please paste outputs of (1) curl head line, (2) openssl issuer/subject, (3) the env grep output (it shouldn’t include secrets). Then I can tell you exactly which fix applies.

Also: you posted your Brave API key earlier — rotate it.

weak quest
#

curl -I https://example.com | head -n 1
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
HTTP/2 200
openssl s_client -connect example.com:443 -servername example.com </dev/null | tail -n 5
depth=3 C = GB, ST = Greater Manchester, L = Salford, O = Comodo CA Limited, CN = AAA Certificate Services
verify return:1
depth=2 C = US, O = SSL Corporation, CN = SSL.com TLS Transit ECC CA R2
verify return:1
depth=1 C = US, O = SSL Corporation, CN = Cloudflare TLS Issuing ECC CA 3
verify return:1
depth=0 CN = example.com
verify return:1
DONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)

#

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null
| openssl x509 -noout -issuer -subject
issuer=C = US, O = SSL Corporation, CN = Cloudflare TLS Issuing ECC CA 3
subject=CN = example.com

mystic pathBOT
#

That output actually nails it:

  • curl works
  • openssl s_client … Verify return code: 0 (ok) works
  • Node fetch fails with UNABLE_TO_GET_ISSUER_CERT_LOCALLY

So your VPS trust store is fine, but Node’s CA bundle/trust selection is not. That’s why:

  • web_fetch fails (TypeError: fetch failed)
  • your CMS HTTPS calls likely fail too
  • the agent then “completes” tasks in text and invents results (no toolResult)

Quick fix: force Node to use the system CA store

Test this first:

#
NODE_OPTIONS=--use-openssl-ca node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

If that prints status 200, make it permanent for the Gateway:

openclaw config set env.NODE_OPTIONS "--use-openssl-ca"
openclaw gateway restart

Then re-test:

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"
#

and re-test web_fetch via /tools/invoke (should return real content now).

If --use-openssl-ca does NOT fix it

Then something is overriding cert paths for Node. Check for these env vars (both in your shell and in ~/.openclaw/.env):

env | grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS' || true
grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS' ~/.openclaw/.env 2>/dev/null || true

If you see SSL_CERT_FILE / SSL_CERT_DIR pointing somewhere custom, remove/unset them and restart the gateway.

Once Node fetch works, your “agent lying” symptom should stop because tools can actually execute and return toolResults again.

#

Perfect — OpenSSL verifies the chain (Verify return code: 0 (ok)) and curl works, but Node’s fetch (undici) doesn’t trust it. That’s why web_fetch fails and you get “fiction” (no toolResult ever succeeds).

This pattern is usually fixed by forcing Node to use the system CA store instead of Node’s bundled CA list.

1) Quick test (should print status 200)

Run:

NODE_OPTIONS=--use-openssl-ca node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

If that works, you’ve found the fix.

2) Make it permanent for the Gateway (systemd service)

Put this in ~/.openclaw/.env on the VPS (create file if missing):

#
echo 'NODE_OPTIONS=--use-openssl-ca' >> ~/.openclaw/.env

Then restart:

openclaw gateway restart

Re-test:

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

3) Confirm web_fetch now works

#

Re-run your /tools/invoke test for web_fetch. Once that returns real content, you should start seeing "role":"toolResult" entries again, and the “lying” symptom should stop.

If the --use-openssl-ca test still fails

Then something is overriding cert paths. Check:

env | grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS|NODE_OPTIONS' || true
grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS|NODE_OPTIONS' ~/.openclaw/.env 2>/dev/null || true

(Especially SSL_CERT_FILE / SSL_CERT_DIR.)

Let me know what the NODE_OPTIONS=--use-openssl-ca node -e … test prints (status or error), and we’ll finish the loop.

weak quest
#

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"
err TypeError: fetch failed
at node:internal/deps/undici/undici:14902:13
at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
[cause]: Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}
}
cause Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}

#

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

#

('err',e); console.error('cause',e.cause);})"
err TypeError: fetch failed
at node:internal/deps/undici/undici:14902:13
at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
[cause]: Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}
}
cause Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}

mystic pathBOT
#

You re-ran the plain node -e … again (no override), so it’s expected to keep failing.

What we need is to run Node using the OS CA store (since OpenSSL/curl trust it, but Node’s bundled CA list apparently doesn’t).

1) Run this exact test (note the NODE_OPTIONS=... prefix)

NODE_OPTIONS=--use-openssl-ca node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"
  • If this prints status 200 → done; we can make it permanent for the Gateway.

2) Make it permanent for the OpenClaw systemd service

Put this into ~/.openclaw/.env (on the VPS):

echo 'NODE_OPTIONS=--use-openssl-ca' >> ~/.openclaw/.env
#
openclaw gateway restart

Then re-test without the prefix:

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

3) If --use-openssl-ca still doesn’t work

Use the explicit system CA bundle path instead (Debian/Ubuntu typically):

ls -l /etc/ssl/certs/ca-certificates.crt
NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"
#

If that works, persist it in ~/.openclaw/.env and restart gateway.

Reply with the output of step 1 (the NODE_OPTIONS=--use-openssl-ca … run). That’s the key fork.

#

You’re still running the plain command (no CA override), so it will keep failing.

Please run this exact command (note the NODE_OPTIONS=... prefix on the same line), then paste the output:

NODE_OPTIONS=--use-openssl-ca node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

If that still fails, run this variant (explicit CA bundle):

ls -l /etc/ssl/certs/ca-certificates.crt
NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"

Once one of those prints status 200, we’ll persist it for the OpenClaw systemd service via ~/.openclaw/.env and restart the gateway.

#

That output looks like you didn’t actually run the NODE_OPTIONS=... prefixed command (the start of what you pasted is mid‑string), so you’re still testing the default Node trust store.

Let’s do two copy/paste-safe tests that avoid quote hell.

1) Test with explicit system CA bundle (most reliable)

First make sure the bundle exists:

ls -l /etc/ssl/certs/ca-certificates.crt

Then run Node using it:

NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt \
node -e 'fetch("https://example.com").then(r=>console.log("status",r.status)).catch(e=>{console.error("err",e); console.error("cause", e.cause);})'
#

If you get status 200, we’re done: Node can now validate Cloudflare’s chain.

Make it permanent for the OpenClaw service

Put it in the service-loaded env file and restart:

echo 'NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt' >> ~/.openclaw/.env
openclaw gateway restart

Then re-test web_fetch via /tools/invoke again — it should stop returning tool execution failed, and you should start seeing "role":"toolResult" entries.

2) Optional: try --use-openssl-ca (nice, but not required)

NODE_OPTIONS=--use-openssl-ca \
node -e 'console.log("execArgv", process.execArgv); fetch("https://example.com").then(r=>console.log("status",r.status)).catch(e=>{console.error("err",e); console.error("cause", e.cause);})'
#

(If execArgv doesn’t show --use-openssl-ca, it didn’t apply.)

Hard warning

Don’t set NODE_TLS_REJECT_UNAUTHORIZED=0 — it “fixes” it by disabling TLS verification and would be a security footgun for your CMS/API keys.

Reply with the output of test #1 (the ls -l … line + the Node status/error).

weak quest
#

node -e "fetch('https://example.com').then(r=>console.log('status',r.status)).catch(e=>{console.error('err',e); console.error('cause',e.cause);})"
err TypeError: fetch failed
at node:internal/deps/undici/undici:14902:13
at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
[cause]: Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}
}
cause Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1697:34)
at TLSSocket.emit (node:events:519:28)
at TLSSocket._finishInit (node:_tls_wrap:1095:8)
at ssl.onhandshakedone (node:_tls_wrap:881:12) {
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
}

mystic pathBOT
#

You’re still running the default node -e … (no CA override), so it will keep failing forever. We need to run Node with the system CA bundle.

Here’s a copy/paste flow that avoids quote issues entirely:

1) Confirm the system CA bundle path exists

ls -l /etc/ssl/certs/ca-certificates.crt

2) Create a tiny test script (no quoting pain)

cat >/tmp/testfetch.js <<'JS'
fetch("https://example.com")
  .then(r => console.log("status", r.status))
  .catch(e => {
    console.error("err", e);
#
    console.error("cause", e.cause);
  });
JS

3) Run it using the system CA bundle

NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt node /tmp/testfetch.js

Expected: status 200

If it’s still failing, paste the output of:

env | grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS|NODE_OPTIONS' || true
#

4) Make it permanent for OpenClaw (so web_fetch works)

Once step (3) prints status 200:

echo 'NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt' >> ~/.openclaw/.env
openclaw gateway restart

Then re-test /tools/invoke web_fetch — you should stop seeing tool execution failed, and tool results should start appearing in transcripts.

(And yep: keep not pasting any gateway tokens.)

weak quest
#

[[reply_to_current]] Looks like a tool event came through (/tools/invoke web_fetch).
What URL should I fetch, and do you want markdown or plain text output?

mystic pathBOT
#

Fetch https://example.com and return it as plain text.

After that, sanity-check that it was a real tool call by confirming you see a "role":"toolResult" entry in the transcript/logs (that’s the “proof” it actually fetched).

If web_fetch still errors with tool execution failed / fetch failed, your VPS still has the Node TLS CA problem (that UNABLE_TO_GET_ISSUER_CERT_LOCALLY), and we need to fix that first—otherwise any “I fetched / I posted” result will keep being fiction.

weak quest
#

[[reply_to_current]] I tried, but the fetch failed for https://example.com/ (network/request error from the fetch tool).
If you want, I can retry now or try fetching a different URL.

mystic pathBOT
#

Don’t change the URL — web_fetch is failing because Node on your VPS can’t verify TLS cert chains (UNABLE_TO_GET_ISSUER_CERT_LOCALLY). Until that’s fixed, any “I fetched / I posted” result will either fail or become hallucinated.

Step 1: check if something is overriding Node’s CA settings

Run on the VPS and paste the output:

env | grep -iE 'SSL_CERT|NODE_EXTRA_CA_CERTS|NODE_OPTIONS' || true

If you see SSL_CERT_FILE or SSL_CERT_DIR set to something custom, that’s often the cause.

Step 2: force Node to use the system CA bundle (copy/paste-safe)

ls -l /etc/ssl/certs/ca-certificates.crt

cat >/tmp/testfetch.js <<'JS'
#
fetch("https://example.com")
  .then(r => console.log("status", r.status))
  .catch(e => {
    console.error("err", e);
    console.error("cause", e.cause);
  });
JS

NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt node /tmp/testfetch.js

Expected: status 200

Step 3: make it permanent for OpenClaw + restart

If the test prints status 200, then: