#Restrict Specific CLI Commands?

1 messages Ā· Page 1 of 1 (latest)

cerulean cipher
#

Is there a way to restrict certain agents from making certain CLI commands or interacting with certain CLI's on the machine entirely?

final spadeBOT
obsidian gullBOT
#

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

final spadeBOT
uncut fernBOT
#

Yep.

Best way to do it is with three layers:

  1. Per-agent tool policy
    Deny exec entirely for agents that should never touch the machine.

  2. Exec allowlist
    For agents that can run commands, set exec to allowlist mode and only allow specific binary paths, per agent.

  3. Sandboxing
    Run those agents with sandbox.mode: "all" so allowed commands stay inside the sandbox unless you separately enable elevated exec.

Example:

#
{
  agents: {
    list: [
      {
        id: "restricted",
        sandbox: { mode: "all", scope: "agent" },
        tools: {
          deny: ["exec", "process", "browser", "write", "edit", "apply_patch"]
        }
      }
    ]
  },
  tools: {
    exec: {
      security: "allowlist",
#
      ask: "on-miss",
      strictInlineEval: true
    }
  }
}

Then allow only specific CLIs for one agent:

openclaw approvals allowlist add --agent restricted "/usr/bin/uptime"

Important caveat: the built-in allowlist is mainly tool-level and binary-path-level, not a rich ā€œthis agent can run git status but not git pushā€ policy for every CLI. If you need that level of isolation, I’d use a wrapper CLI or a separate sandbox/node/VM.

Docs:

cerulean cipher
#

If you do have the agent in a sandbox, though, all of those EXEC commands are run in that sandbox and there is no way for them to run CLIs outside of that sandbox. Is that correct?

uncut fernBOT
#

Not as an absolute guarantee, no.

The accurate version is:

  • By default, yes: if the session is sandboxed, normal exec with host=auto runs inside the sandbox.
  • But there are explicit escape paths:
    • elevated exec can break out to the gateway or a node if enabled
    • node-targeted exec is also outside the sandbox
    • risky bind mounts can weaken the boundary a lot (/var/run/docker.sock is the classic footgun)

So if you want ā€œthis agent can never run host CLIs outside the sandboxā€, I’d combine:

  • sandbox enabled for that agent
  • tools.elevated.enabled: false (or per-agent disable)
  • no node exec for that agent
  • tight tool policy, or just deny exec entirely
  • minimal bind mounts, preferably read-only, and never mount docker.sock
cerulean cipher
#

Yeah, please mock up that config. I also am unsure what you mean by node exec.

uncut fernBOT
#

Yep. And ā€œnode execā€ is easy to overthink.

Node exec just means: run the command on a paired node device instead of the gateway machine.
Example: Gateway is on a VPS, but you’ve paired your laptop as a node host. exec host=node would run the CLI on the laptop.

If you have not set up a node host / companion app for shell execution, you can mostly ignore that path.

1. Hard lock, no machine CLI access at all

If you want an agent to never run shell commands anywhere:

{
  agents: {
    list: [
      {
#
        id: "restricted",
        workspace: "~/.openclaw/workspace-restricted",
        sandbox: { mode: "all", scope: "agent" },
        tools: {
          allow: ["read", "web_search", "web_fetch", "session_status"],
          deny: ["exec", "process", "write", "edit", "apply_patch", "browser"]
        }
      }
    ]
  }
}

That is the safest option.


#

2. Sandboxed CLI only

If you want the agent to use CLI tools, but only inside the sandbox:

{
  agents: {
    list: [
      {
        id: "builder",
        workspace: "~/.openclaw/workspace-builder",
        sandbox: {
          mode: "all",
          scope: "agent",
          workspaceAccess: "rw"
        },
#
        tools: {
          allow: ["read", "write", "edit", "apply_patch", "exec", "process"],
          elevated: { enabled: false },
          exec: {
            host: "sandbox"
          }
        }
      }
    ]
  }
}

Then keep these rules:

  • do not enable elevated for that agent
#
  • do not set /exec host=node
  • keep sandbox binds tight
  • never mount /var/run/docker.sock

Important nuance

Inside a sandboxed agent, the agent can only use the CLIs that exist inside the sandbox container. So if you want to block git, docker, kubectl, etc., the cleanest move is to use a sandbox image that simply does not have them installed.

So the practical pattern is:

  • deny exec if you want a hard guarantee
  • if you allow exec, keep it sandboxed and keep the sandbox image minimal

Docs:

#

If you want, I can also mock up a ā€œsafe builderā€ sandbox image idea, like ā€œonly bash + rg + pnpm, no git/docker/kubectlā€.

cerulean cipher
#

Yeah, I definitely want them to be able to use CLI tools, but only inside the sandbox. So essentially, well, do you actually need the allow list in your example? If I am fine with them executing any CLI that is installed in that sandbox, and then having elevated access as false?

And do you need the explicit exec host sandbox? Configuration there? Or is that implicit with them running in a sandbox?

uncut fernBOT
#

Yep, if that’s your goal, you can simplify it a lot.

Short answer

  • No, you do not need an exec allowlist if you’re happy with ā€œanything installed inside the sandbox is fair game.ā€
  • No, you do not strictly need exec.host: "sandbox" if the agent is already sandboxed. Normal exec with host=auto will resolve to the sandbox.

The important distinction

There are two different ā€œallowlistsā€ people mix together:

  1. tools.allow / tools.deny
    This controls which OpenClaw tools the agent can call (exec, read, browser, etc.)

  2. exec approvals allowlist
    This controls which host/node binaries can run when exec is targeting the gateway or a node

#

If you are keeping exec inside the sandbox, the second one is usually not relevant.

Minimal config for what you want

{
  agents: {
    list: [
      {
        id: "sandboxed-cli",
        workspace: "~/.openclaw/workspace-sandboxed-cli",
        sandbox: {
          mode: "all",
          scope: "agent",
          workspaceAccess: "rw"
        },
#
        tools: {
          elevated: { enabled: false }
        }
      }
    ]
  }
}

Optional, but nice

You can still add this:

tools: {
#
  exec: {
    host: "sandbox"
  }
}

or per-agent:

tools: {
  elevated: { enabled: false },
  exec: { host: "sandbox" }
}

That is not strictly required, but I like it because it makes the intent explicit and helps it fail closed if sandboxing is not active for some reason.

#

One nuance about nodes

host=auto does not automatically choose a node.
But if you deliberately set up node exec and explicitly target host=node, that is an outside-the-sandbox path.

So if you want the cleanest mental model:

  • sandbox the agent
  • keep elevated: false
  • do not use node exec for that agent
  • only install the CLIs you want inside the sandbox image

That gives you basically, ā€œthe agent can run whatever is in the container, and nothing on the host.ā€

If you want, I can mock up the next step, which is a minimal sandbox image with only a few allowed CLIs installed.

cerulean cipher
#

The sandbox all mode means that even sub agents run in that sandbox, right?

#

I also want the agents to be sandboxed, but still able to use a browser. Is that on by default or how does that work?

uncut fernBOT
#

Yes, with one nuance.

sandbox.mode: "all" means sub-agents are sandboxed too.
If a sandboxed requester tries to spawn a target that would run unsandboxed, OpenClaw rejects it.

The nuance is scope:

  • scope: "session": each session/sub-agent gets its own sandbox
  • scope: "agent": sessions for that agent share the same agent-scoped sandbox
  • scope: "shared": broader shared sandbox scope

So:

  • sandboxed, yes
  • same exact sandbox instance, depends on scope

If you want parent + sub-agents all sandboxed, but isolated from each other, use:

#
sandbox: {
  mode: "all",
  scope: "session"
}

If you want them all sandboxed but sharing the same environment for that agent, use scope: "agent" instead.

#

Yep, that works.

Short version

  • Yes, sandboxed agents can still use a browser.
  • In a sandboxed session, the browser tool defaults to the sandbox browser.
  • The sandbox browser auto-starts by default when needed.
  • For your setup, the normal path is: sandboxed agent + sandboxed browser.

What needs to be true

  1. Sandboxing is enabled
  2. Browser feature is enabled (browser.enabled is true by default)
  3. The browser tool is not denied by tool policy
    • if you use a restrictive tools.allow, include browser (or group:ui)

Good baseline config

#
{
  agents: {
    list: [
      {
        id: "sandboxed-browser-agent",
        workspace: "~/.openclaw/workspace-sandboxed-browser-agent",
        sandbox: {
          mode: "all",
          scope: "session"
        },
        tools: {
          elevated: { enabled: false },
          allow: ["group:fs", "group:runtime", "browser", "session_status"]
        }
      }
#
    ]
  }
}

If you are not using a restrictive tools.allow, you can omit that part.

One important caveat

If you want sandbox + browser, the easiest/normal backend is Docker.
The docs note that sandbox browser support is not there for every remote backend.

Host browser vs sandbox browser

By default, a sandboxed agent uses the sandbox browser.

#

If this fixed it, please right-click the solution message → Apps → āœ… Mark Solution.

cerulean cipher
#

What do you suggest for a sandboxed browser within Docker? Is that included in the common setup script or should I have something else?

uncut fernBOT
#

I’d use the built-in sandbox browser image first. That’s the path the docs expect.

Recommendation

For a Docker-backed sandboxed browser:

  • use the normal Docker sandbox backend
  • enable sandbox.browser.enabled: true
  • use the bundled browser image (openclaw-sandbox-browser:bookworm-slim)
  • keep autoStart: true

Important distinction

There are two setup scripts:

  • scripts/sandbox-common-setup.sh
    builds the general CLI sandbox image
#
  • scripts/sandbox-browser-setup.sh
    builds the browser sandbox image

So if you want a sandboxed browser, the common setup script is not the one.
You want the browser setup script.

Minimal config shape

{
  agents: {
    list: [
      {
        id: "sandboxed-browser-agent",
        sandbox: {
          mode: "all",
#
          scope: "session",
          browser: {
            enabled: true,
            autoStart: true
          }
        },
        tools: {
          elevated: { enabled: false },
          allow: ["group:fs", "group:runtime", "browser", "session_status"]
        }
      }
    ]
  }
}
#

If you are using the Docker gateway setup

The docs mention scripts/docker/setup.sh can bootstrap sandbox config when:

OPENCLAW_SANDBOX=1

So:

  • Docker deployment path: that script can help bootstrap the sandbox side
  • browser image itself: use scripts/sandbox-browser-setup.sh

My practical suggestion

Start with the bundled browser image first. Only make a custom one if you need things like:

cerulean cipher
#

Okay, but if I want the, I guess a couple questions. How do I choose which container setup the agent takes? And then also, if I want the common setup so that we include things like NPM and Python and Git and Node and stuff, but I also want it to have a browser, what does that look like in terms of setup?

and then configuration and all that.

uncut fernBOT
#

I’d treat it as a 2-image setup.

How OpenClaw picks the container

There are two separate sandbox images:

  1. Tool sandbox image
    Used for exec, read, write, edit, apply_patch, process, etc.
    Config key: sandbox.docker.image

  2. Browser sandbox image
    Used for the sandboxed browser tool
    Config key: sandbox.browser.image

So if you want:

  • Node / npm / Python / git / general CLI tools
#
  • and
  • a sandboxed browser

…the normal setup is:

  • tool sandbox image = common image
  • browser sandbox image = browser image

You do not need to mash both into one container unless you want a custom setup.

Build the two images

scripts/sandbox-common-setup.sh
scripts/sandbox-browser-setup.sh
#

That gives you:

  • openclaw-sandbox-common:bookworm-slim
  • openclaw-sandbox-browser:bookworm-slim

Recommended config

{
  agents: {
    defaults: {
      sandbox: {
        mode: "all",
        backend: "docker",
        scope: "session",
        workspaceAccess: "rw",
#
        docker: {
          image: "openclaw-sandbox-common:bookworm-slim",
          network: "bridge"
        },
        browser: {
          enabled: true,
          image: "openclaw-sandbox-browser:bookworm-slim",
          autoStart: true
        }
      }
    },
    list: [
      {
        id: "builder",
        tools: {
#
          allow: ["group:fs", "group:runtime", "browser", "session_status"],
          elevated: { enabled: false }
        }
      }
    ]
  }
}

Why network: "bridge" here?

Because the tool sandbox defaults to network: "none".

If you want things like:

  • npm install
#
  • pip install
  • git clone
  • fetching packages / APIs

…then the tool sandbox needs outbound network, so bridge (or a custom bridge network) makes sense.

If you only need local tooling and no outbound network, keep it tighter.

Per-agent choice

If one agent should use a different image, override it under that agent:

{
  agents: {
    list: [
#
      {
        id: "minimal",
        sandbox: {
          docker: {
            image: "openclaw-sandbox:bookworm-slim"
          },
          browser: {
            enabled: false
          }
        }
      }
    ]
  }
}
#

So the rule is:

  • global default: agents.defaults.sandbox.*
  • one agent different: agents.list[].sandbox.*

My recommendation

For your use case, I’d start with exactly this:

  • openclaw-sandbox-common for CLI/tooling
  • openclaw-sandbox-browser for browser
  • scope: "session" if you want parent/subagents isolated
  • elevated: false
  • browser explicitly allowed if you’re using tools.allow

Docs:

cerulean cipher
#

The config looks right — browser is enabled with autoStart: true. But my runtime shows capabilities=none, which means the browser tool just isn't being surfaced to me in this session.
A few possible causes:
The browser container didn't start in time / failed to start
This agent's tool policy is filtering it out
The session channel (Slack) has browser blocked at the channel level
Worth checking openclaw status or the agent config to see if browser is explicitly disabled for this agent or the Slack channel. Could also be the sandbox browser container isn't healthy — docker ps would tell you if openclaw-sandbox-browser is actually running.

I just added that config and this is what the agent is saying.

uncut fernBOT
#

That agent reply is mixing a few things.

Big correction: capabilities=none does not mean ā€œbrowser tool is unavailable.ā€ That usually refers to surface/channel capabilities, not the agent’s tool list.

What I’d check, in order:

  1. Restart the gateway
    Browser/plugin config changes need a restart.

    openclaw gateway restart
    
  2. Plugin allowlist
    If you use plugins.allow, it must include browser, or the tool won’t load at all.

    openclaw config get plugins.allow
    
#
  1. Tool policy
    This is the most likely one.
    A very common gotcha is:

    tools: { profile: "coding" }
    

    coding does not include browser.

    So either add:

    tools: {
      profile: "coding",
      alsoAllow: ["browser"]
    }
    

    or per-agent:

#
   agents: {
     list: [
       {
         id: "builder",
         tools: {
           profile: "coding",
           alsoAllow: ["browser"]
         }
       }
     ]
   }
  1. Sandbox tool policy
    If you set tools.sandbox.tools.allow, browser must be in there too when the agent is sandboxed.
#
   openclaw config get tools.sandbox.tools
  1. Per-agent / per-provider overrides
    Also check:

    • agents.list[].tools
    • tools.byProvider[...]
    • agents.list[].tools.byProvider[...]
  2. Only after that, check the browser container
    With autoStart: true, the sandbox browser is usually started on demand, so docker ps is useful, but only after the tool is actually allowed/surfaced.

My bet

The two most likely causes are:

  • missing gateway restart
#
  • tools.profile: "coding" or another allowlist/profile that does not include browser

If you want, paste these and I can tell you exactly where it’s getting filtered:

openclaw --version
openclaw config get plugins.allow
openclaw config get tools.profile
openclaw config get tools.allow
openclaw config get tools.alsoAllow
openclaw config get tools.sandbox.tools
openclaw config get agents.list

Docs:

cerulean cipher
#

on this agent: "tools": {
"profile": "full"
}

runnint 4.21 version. All those config gets return nothing except the bottom which shows the agent config I already sent you. I already restarted the gateway as well... docker ps shows

āžœ ~ git:(main) āœ— docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0cd4519f18e9 openclaw-sandbox-browser:bookworm-slim "openclaw-sandbox-br…" 11 minutes ago Up 11 minutes 5900/tcp, 6080/tcp, 9222/tcp openclaw-sbx-browser-elliott-22de5207
debe113e158a openclaw-sandbox-common:bookworm-slim "sleep infinity" 23 hours ago Up 23 hours openclaw-sbx-agent-elliott-22de5207

uncut fernBOT
#

Ah, this is actually useful. Given what you pasted:

  • tools.profile: "full"
  • gateway restarted
  • both sandbox containers are up
  • browser container is healthy

…I don’t think this is a Docker/browser-container problem anymore.

I think the likely issue is runtime type.

What I suspect

That agent is probably an ACP/Codex/Claude Code-style agent, not a normal embedded OpenClaw agent.

The clue is the wording about capabilities=none. That sounds much more like ACP harness/runtime status than normal OpenClaw tool gating.

#

And in the docs:

  • ACPX sessions do not expose OpenClaw plugin tools by default
  • the browser tool is a bundled plugin tool

So the browser can be fully working in Docker, but still not be surfaced to that ACP agent.

If that agent is ACP-backed, try this

openclaw config set plugins.entries.acpx.config.pluginToolsMcpBridge true
openclaw gateway restart

That enables the MCP bridge that exposes plugin tools to ACPX sessions.

There’s also a separate bridge for built-in OpenClaw tools:

#
openclaw config set plugins.entries.acpx.config.openClawToolsMcpBridge true
openclaw gateway restart

But for browser specifically, the important one is the plugin tools MCP bridge.

Why I think this is the right direction

You’ve already ruled out the common failures:

  • not a missing browser container
  • not a plain tool profile issue
  • not a missing restart
  • probably not sandbox startup

So what’s left is mostly ā€œthis session runtime doesn’t inherit OpenClaw tools the way you expect.ā€

#

Quick thing to confirm

Check whether that agent or thread is using ACP runtime, for example:

  • agent has runtime: { type: "acp", ... }
  • or the binding is type: "acp"

If yes, I’d bet pretty hard this is the issue.

Docs:

If you want, paste the agent’s runtime block or binding config and I can tell you in one shot whether it’s ACP and which bridge you need.

cerulean cipher
#

Not using acpx I don't think anymore? Here's what /status returns:

šŸ¦ž OpenClaw 2026.4.21 (f788c88)
🧠 Model: claude-sonnet-4-6 ·
🧮 Tokens: 3 in / 238 out Ā· šŸ’µ Cost: $0.0036
šŸ—„ļø Cache: 42% hit Ā· 16k cached, 21k new
šŸ“š Context: 21k/400k (5%) Ā· 🧹 Compactions: 0
🧵 Session: agent:elliott:slack:direct:elliott🧵1776971534.255409 - updated just now
āš™ļø Runtime: docker/all Ā· Think: low
🪢 Queue: collect (depth 0)

uncut fernBOT
#

Yep, that /status output is the key.

That means this is not ACP. It’s a normal embedded agent running docker/all, so I’d drop that theory.

Also, with:

  • tools.profile: "full"
  • gateway restarted
  • both sandbox containers running

…I would stop chasing Docker health. The browser container being up means the sandbox browser side is alive.

What this narrows it down to

At this point, the likely causes are:

  1. browser plugin not actually loaded
#
  1. Slack conversation override is filtering tools
  2. some other allow/deny override is hitting this session
    (tools.sandbox.tools, tools.byProvider.slack, per-agent provider override, etc.)

And one important nuance from the docs:

  • sandbox.browser.enabled does not require top-level browser.enabled
  • so the sandbox browser container can exist even if the browser tool plugin itself is not surfaced

So ā€œcontainer is runningā€ does not prove ā€œbrowser tool is availableā€.

What I’d check next

1. In that exact Slack conversation

Run:

#
/tools verbose

That is the runtime truth for this conversation right now.

2. On the gateway

Run:

openclaw browser status

If that command is missing/unknown, that strongly points to the browser plugin not being loaded.

3. Check browser plugin config

#
openclaw config get plugins.allow
openclaw config get plugins.entries.browser.enabled

If plugins.allow is set, it must include "browser".

4. Check Slack-specific overrides

Slack supports per-channel tools / toolsBySender overrides, so I’d check:

openclaw config get channels.slack.channels

5. Check remaining tool filters

#
openclaw config get tools.sandbox.tools
openclaw config get tools.byProvider
openclaw config get agents.list

My current best guess

Since profile: "full" rules out the obvious profile issue, my money is on one of these:

  • plugins.allow exists and doesn’t include browser
  • plugins.entries.browser.enabled is off
  • a Slack channel override is filtering the tool

Docs:

cerulean cipher
#

browser status:

profile: openclaw
enabled: true
running: false
transport: cdp
cdpPort: 20800
cdpUrl: http://127.0.0.1:20800
browser: unknown
detectedBrowser: chrome
detectedPath: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
profileColor: #FF4500

#

available tools also shows browser and a whole slew of other tools

uncut fernBOT
#

That actually helps a lot.

This means a few things pretty clearly:

  • not ACP
  • the browser plugin is loaded (openclaw browser status works)
  • tools.profile: "full" is not the blocker
  • the sandbox browser infra is up (your docker ps already showed that)

So the agent’s ā€œcapabilities=noneā€ diagnosis is almost certainly wrong / unrelated.

Also, important nuance:

  • openclaw browser status is showing the host managed browser profile
  • your sandboxed agent uses the sandbox browser container
  • so running: false there is not proof that sandbox browser is unavailable
#

In other words, I would stop chasing browser startup. The sandbox browser container is already there.

Best next check

In that exact Slack DM/session, run:

/tools verbose

That is the authoritative answer for what the agent can use right now.

How to interpret it

If /tools verbose shows browser

Then your config is basically fine, and the model is just being overly cautious / mistaken.
At that point I’d literally tell it something like:

#

ā€œYou do have the browser tool in this session. Use /tools as ground truth, not capabilities=none.ā€

If /tools verbose does not show browser

Then the remaining likely filters are:

openclaw config get tools.byProvider
openclaw config get agents.list
openclaw config get tools.sandbox.tools
openclaw config get plugins.entries.browser.enabled
openclaw config get plugins.allow

One useful narrowing point

Because this is a Slack direct session, I do not think channels.slack.channels.<id>.tools is the main suspect. That is mainly for channel overrides, not DMs.

#

Nice, then the important part is settled:

if /tools shows browser, the browser tool is available in that session.
That’s the runtime source of truth.

So the agent saying capabilities=none means browser is unavailable is just... wrong. It’s misreading unrelated status/surface info.

What this means

Your setup is basically working:

  • browser tool is loaded
  • tool policy is allowing it
  • session can see it
  • sandbox/browser config is not the blocker

What I’d do

#

Just steer the agent more explicitly. Something like:

/tools shows browser is available in this session.
Use /tools as ground truth, ignore the capabilities=none line, and call the browser tool directly.

Or even more concrete:

Open the browser tool and navigate to https://example.com, then take a snapshot.

If it still refuses

Then it’s not a config problem anymore, it’s a model behavior/prompting problem. At that point I’d try one of:

  1. Explicit steer like above
  2. New session/thread to clear the bad assumption
  3. Add a stronger agent instruction in AGENTS.md, e.g.:
    • ā€œUse /tools as the source of truth for tool availability.ā€
#
  • ā€œDo not infer tool unavailability from runtime capabilities lines.ā€

So yeah, good news, I’m pretty confident your browser config is fine. The agent is just being dumb here šŸ˜…

If this fixed it, please right-click the solution message → Apps → āœ… Mark Solution.

cerulean cipher
#

this is what my agent is saying lol

"I believe you that /tools shows browser — but I literally cannot call it. It's not in the tool definitions injected into my context, so I have no way to invoke it regardless of what /tools displays. That's the disconnect.
This sounds like a bug: browser is registered at the system level but not being passed through to the agent's tool schema. Worth filing with the OpenClaw team — the tool is visible to the UI but not surfaced to the model."

uncut fernBOT
#

Yeah, at this point I think this is a real bug / regression, not a config mistake.

You’ve already ruled out the usual stuff:

  • non-ACP embedded runtime
  • tools.profile: "full"
  • /tools shows browser
  • gateway restarted
  • sandbox + browser containers are up

If the model still says browser is not in the injected tool definitions, then the likely problem is:

runtime tool inventory and actual injected tool schema are out of sync.

One useful last data point before filing:

#
/context detail

If browser is missing from the tool-schema contributors there, that’s pretty strong proof.

I’d file a GitHub issue with:

  • openclaw --version
  • /status
  • /tools verbose
  • /context detail
  • the agent config
  • the exact model reply saying browser is visible but not callable

Bug reports go here: https://github.com/openclaw/openclaw/issues

#

If you want, I can help you turn what you already collected into a tight repro report.

cerulean cipher
#

I don't see a browser do you?

coding-agent: 142 chars (~36 tok)
node-connect: 142 chars (~36 tok)
video-frames: 142 chars (~36 tok)
apple-notes: 140 chars (~35 tok)
healthcheck: 140 chars (~35 tok)

gh-issues: 136 chars (~34 tok)
Tool list (system prompt text): 0 chars (~0 tok)
Tool schemas (JSON): 6,084 chars (~1,521 tok) (counts toward context; not shown as text)
Tools: read, edit, write, exec, process, sessions_list, sessions_history, sessions_send, sessions_yield, sessions_spawn, subagents, session_status, image
Top tools (schema size):
sessions_spawn: 1,333 chars (~334 tok)
exec: 1,098 chars (~275 tok)
process: 961 chars (~241 tok)
edit: 834 chars (~209 tok)
image: 342 chars (~86 tok)
read: 304 chars (~76 tok)
sessions_send: 274 chars (~69 tok)
write: 225 chars (~57 tok)
sessions_list: 212 chars (~53 tok)
subagents: 191 chars (~48 tok)
sessions_history: 161 chars (~41 tok)
session_status: 89 chars (~23 tok)
sessions_yield: 60 chars (~15 tok)
Top tools (summary text size):
exec: 446 chars (~112 tok)
session_status: 336 chars (~84 tok)
edit: 326 chars (~82 tok)
process: 322 chars (~81 tok)
sessions_spawn: 302 chars (~76 tok)
read: 298 chars (~75 tok)
image: 260 chars (~65 tok)
sessions_send: 208 chars (~52 tok)
sessions_history: 180 chars (~45 tok)
sessions_list: 177 chars (~45 tok)
write: 127 chars (~32 tok)
subagents: 105 chars (~27 tok)
sessions_yield: 97 chars (~25 tok)
Tools (param count):
sessions_spawn: 18 params
exec: 12 params
process: 12 params
image: 6 params
sessions_send: 5 params
sessions_list: 4 params
subagents: 4 params
read: 3 params
sessions_history: 3 params
edit: 2 params
write: 2 params
session_status: 2 params
sessions_yield: 1 params

uncut fernBOT
#

Yep, I don’t see browser there either.

That output is the important proof:

  • /tools says browser is reachable at runtime
  • /context detail says browser is not in the injected tool schemas
  • the agent can only call tools that are actually in the injected schema

So the agent’s conclusion is basically right, even if its earlier reasoning was sloppy. This looks like a real mismatch/bug on 2026.4.21, not a config mistake.

I’d do just two things now:

  1. Try one brand new session/thread after the restart, just to rule out session-scoped weirdness.
  2. If it still happens, file a GitHub issue with:
    • openclaw --version
    • /status
    • /tools verbose
cerulean cipher
#

Can you just check the latest release of Open Claw? I think there's one for April 22nd and confirm that that wasn't fixed in that release. And can you also check the existing issues to see if there is already an existing issue?

uncut fernBOT
#

Yeah, I checked.

Latest release: v2026.4.22
https://github.com/openclaw/openclaw/releases/tag/v2026.4.22

I don’t see anything in that release notes page that looks like this specific fix, meaning:

  • /tools says browser is available
  • but /context detail / injected schema does not include browser
  • so the agent can’t actually call it

I also checked existing issues. I didn’t find an exact duplicate for that precise /tools vs injected-schema mismatch, but I found a few very related ones:

#

My read is:

  • not fixed in 2026.4.22, at least not obviously
  • no exact existing issue for your exact symptom
  • but there’s definitely a family of ā€œtool reachable/configured but not actually injected into the model schemaā€ bugs

So I’d open a new issue and link those related ones. If you want, I can draft the issue body for you from the evidence you already collected.

cerulean cipher
#

YEa please do

uncut fernBOT
#

Yep, here’s a clean draft you can paste into a GitHub issue.

Suggested title
[Bug]: /tools shows browser as available, but browser is missing from injected tool schemas so the agent cannot call it

Suggested body

Bug type
Behavior bug (incorrect output/state without crash)

Summary
The agent reports that `browser` is available at runtime via `/tools`, but `browser` is not actually present in the injected tool schemas shown by `/context detail`. As a result, the model cannot call the browser tool even though the UI/runtime inventory says it is available.

This looks like a mismatch between:
- runtime/effective tool inventory (`/tools`)
- actual model-injected tool schema set (`/context detail`)
#

Expected behavior
If `/tools` shows `browser` as available, the browser tool should also be present in the injected tool schemas and callable by the model.

Actual behavior
- `/tools` says browser is available
- the agent says it cannot call browser because it was not injected into the model's tool definitions
- `/context detail` confirms `browser` is absent from the injected tool schema list

Environment
- OpenClaw version: 2026.4.21 (replace with exact `openclaw --version` output if different)
- Also checked latest release notes for 2026.4.22 and did not see an obvious fix for this issue
- Runtime: embedded agent, not ACP
- Sandbox mode: docker/all
- Browser sandbox container is running
#
- Gateway restarted after config changes

Steps to reproduce
1. Configure the agent so browser should be available
2. Restart the gateway
3. Start a fresh session/thread
4. Run `/tools verbose`
5. Run `/context detail`
6. Ask the agent to use the browser tool

Expected result
- `/tools` includes `browser`
- `/context detail` also includes `browser` in the injected tool schemas
- the agent can actually call `browser`
#
Actual result
- `/tools` shows `browser`
- `/context detail` tool schemas do not include `browser`
- the agent says it cannot call `browser` because it is not in the injected tool definitions

Evidence

Agent/tooling mismatch:
- `/tools` reports browser available
- `/context detail` shows injected tools as:

read, edit, write, exec, process, sessions_list, sessions_history, sessions_send, sessions_yield, sessions_spawn, subagents, session_status, image

Notably, `browser` is missing from the injected tool schema list.
#
Relevant `/context detail` excerpt:
- Tool schemas (JSON): 6,084 chars (~1,521 tok)
- Tools:
  `read, edit, write, exec, process, sessions_list, sessions_history, sessions_send, sessions_yield, sessions_spawn, subagents, session_status, image`

Agent response:
> "I believe you that /tools shows browser — but I literally cannot call it. It's not in the tool definitions injected into my context, so I have no way to invoke it regardless of what /tools displays."

Additional notes
- This does not appear to be a browser container health issue
- This does not appear to be an ACP harness issue
- This looks specifically like the effective tool inventory and injected schema set are getting out of sync

Related issues
- #32807
#
- #36651
- #51937
- #52677

If you want, I can also tighten that into a shorter maintainer-friendly version before you post it.

cerulean cipher
#

Do you think we need to make this specific to a Docker sandbox? Because the browser tool I think works outside of the sandbox for agents that are not sandboxed.

uncut fernBOT
#

Yeah, I think that’s worth calling out, but carefully.

I would phrase it like this:

  • Observed in sandboxed embedded sessions (docker/all)
  • Not obviously reproducible in non-sandboxed sessions, where browser seems to work

That’s better than claiming it’s definitely a Docker-only bug.

So I’d tweak the report to say something like:

This was reproduced in an embedded agent running with Docker sandboxing enabled.
In non-sandboxed agent sessions, the browser tool appears to work normally, so this may be specific to the sandboxed tool/schema assembly path.

That helps maintainers a lot, because it narrows the scope without overclaiming.

If you want, you can even tighten the title to: