Describe the bug
When a session runs on a claude-cli backend, the loopback MCP bridge excludes native tools (read, write, edit, apply_patch, exec, process) via NATIVE_TOOL_EXCLUDE to avoid duplicating tools the harness provides natively. However, these exclusions are merged into the session's explicitDenylist in resolveGatewayScopedTools() alongside real policy denies, making them indistinguishable.
This has cascading effects beyond the ACP spawn issue (filed separately):
- Subagent capability resolution sees the caller as having tool denies that don't actually exist as policy
inheritedToolDenylist propagates dedup exclusions to all child sessions (subagent and ACP)
- Tool policy introspection (e.g.,
resolveToolProfilePolicy) reports false denies for CLI-backend sessions
- Any future feature that reads
explicitDenylist or inheritedToolDenylist will be affected
The design gap
There are two semantically different reasons to exclude a tool from a session's tool set:
| Reason |
Source |
Should inherit? |
Should block ACP? |
| Policy deny |
Config, sender, agent, group policy |
Yes |
Yes |
| Dedup exclusion |
Loopback MCP bridge (NATIVE_TOOL_EXCLUDE) |
No |
No |
Currently both flow through the same { deny: [...] } policy object and end up in the same explicitDenylist array. The tool-resolution.ts resolver has no way to tell them apart.
Affected code path
mcp-http.runtime.ts: excludeToolNames: NATIVE_TOOL_EXCLUDE
↓
tool-resolution.ts: params.excludeToolNames → { deny: excludedToolNames }
→ merged into explicitDenylist
→ copied to inheritedToolDenylist
↓
acp-spawn.ts: findAcpUnsupportedInheritedToolDeny() → BLOCKED
subagent-capabilities.ts: inherits false denies
Suggested approach
Separate the two exclusion types at the resolveGatewayScopedTools level:
excludeToolNames should filter the current session's tool array only (as it does now)
excludeToolNames should not contribute to explicitDenylist or inheritedToolDenylist
- Alternatively, introduce a
scope or reason field on deny entries so downstream consumers can distinguish policy from dedup
Impact
- Any user running OpenClaw with a
claude-cli backend as their primary model cannot spawn ACP sessions from chat (the most natural workflow)
- Workaround: temporarily switch to an API-backed model before spawning ACP, then switch back — functional but defeats the purpose of having a default model
Environment
- OpenClaw version: 2026.5.28
- Relevant source:
tool-resolution.ts, mcp-http.runtime.ts, inherited-tool-deny.ts
Describe the bug
When a session runs on a claude-cli backend, the loopback MCP bridge excludes native tools (
read,write,edit,apply_patch,exec,process) viaNATIVE_TOOL_EXCLUDEto avoid duplicating tools the harness provides natively. However, these exclusions are merged into the session'sexplicitDenylistinresolveGatewayScopedTools()alongside real policy denies, making them indistinguishable.This has cascading effects beyond the ACP spawn issue (filed separately):
inheritedToolDenylistpropagates dedup exclusions to all child sessions (subagent and ACP)resolveToolProfilePolicy) reports false denies for CLI-backend sessionsexplicitDenylistorinheritedToolDenylistwill be affectedThe design gap
There are two semantically different reasons to exclude a tool from a session's tool set:
NATIVE_TOOL_EXCLUDE)Currently both flow through the same
{ deny: [...] }policy object and end up in the sameexplicitDenylistarray. Thetool-resolution.tsresolver has no way to tell them apart.Affected code path
Suggested approach
Separate the two exclusion types at the
resolveGatewayScopedToolslevel:excludeToolNamesshould filter the current session's tool array only (as it does now)excludeToolNamesshould not contribute toexplicitDenylistorinheritedToolDenylistscopeorreasonfield on deny entries so downstream consumers can distinguish policy from dedupImpact
claude-clibackend as their primary model cannot spawn ACP sessions from chat (the most natural workflow)Environment
tool-resolution.ts,mcp-http.runtime.ts,inherited-tool-deny.ts