openclaw - ✅(Solved) Fix Agent declared tools != effective session tools: no strict inheritance mode, no visibility, no clipping reason trace [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#44484Fetched 2026-04-08 00:46:18
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1

OpenClaw currently allows configuring tool permissions at the agent level (for example agents.list[].tools.profile = "full" or explicit allow/deny rules), but in practice the tools available to a concrete runtime session can still be reduced by subagent/session/runtime-specific behavior.

This creates a mismatch between:

  • declared agent tool permissions
  • effective session tool permissions

The result is that an agent may appear to have exec in its declared tool set, but a real session can still say it cannot execute commands.

This is not just a documentation issue — it creates operational uncertainty and breaks the expectation that agent tool policy is authoritative.


Root Cause

For multi-agent workflows, operators need tool behavior to be:

  • deterministic
  • inspectable
  • explainable

Without that, agent-level tool configuration is misleading because it looks authoritative but is not actually final.


Fix Action

Fixed

PR fix notes

PR #59898: fix(agents): handle explicit empty tool lists in system prompt

Description (problem / solution / changelog)

Summary

Describe the problem and fix in 2-5 bullets:

  • Problem: OpenClaw treated an explicit empty effective tool set as if tools were merely unspecified, so a real no-tool session still inherited fallback tool inventory plus later tool-only prompt guidance.
  • Why it matters: A user can intentionally create a no-tool session in a deployed OpenClaw instance, and that session should not receive misleading instructions about tools, subagents, approvals, sandbox execution, or other capabilities it cannot actually use.
  • What changed: Explicit empty-tool sessions now render as genuinely no-tool sessions. They no longer receive fallback tool inventory or later tool-only prompt sections, the prompt report keeps tools.entries = [] with tools.listChars === 0, and the prompt still preserves non-tool context such as workspace notes and TTS hints where applicable.
  • What did NOT change (scope boundary): This PR does not broadly rework capability gating for all prompt sections. Omitted tool lists and explicit non-empty filtered tool lists are kept as close as possible to upstream/main behavior. This PR does not claim to fix unrelated model-level or workflow-level regressions outside prompt construction/reporting for explicit empty-tool sessions.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes N/A
  • Related #25569, #53000, #46214
  • Adjacent context: #44229, #44484, #47583
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

  • Root cause: src/agents/system-prompt.ts did not distinguish between "tool list omitted" and "effective tool list explicitly empty". That let explicit no-tool sessions fall through legacy fallback and later unconditional prompt sections, so the tooling section and several later sections still implied tool access.
  • Missing detection / guardrail: There was no test that separately locked in behavior for omitted toolNames, explicit empty toolNames: [], and explicit non-empty filtered tool lists across both prompt text and prompt-report rendering.
  • Prior context (git blame, prior PR, issue, or refactor if known): #25569 is the closest related upstream work because it also distinguishes explicitly empty tool availability from unspecified/default availability, but it is broader in scope. #53000 addresses the same general empty-tools confusion but still keeps a reference tool catalog in the prompt. #46214 removes adjacent CLI-runner prompt pollution rather than this exact bug.
  • Why this regressed now: The prompt builder still had legacy/default fallback behavior and unconditional later prompt sections, but no dedicated explicit-empty branch. Once a real runtime session produced an intentionally empty effective tool set, it still inherited those fallback and unconditional sections.
  • If unknown, what was ruled out: This was not just a source-level or unit-only mismatch. The same class of problem was reproduced via the normal OpenClaw CLI against a live isolated deployment config, without directly calling prompt-builder functions.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • src/agents/system-prompt.test.ts
    • src/agents/system-prompt-report.test.ts
  • Scenario the test should lock in:
    • explicit empty-tool sessions must not inject fallback tool inventory
    • explicit empty-tool sessions must not leak later tool-only guidance such as tool-call style, subagent/tool messaging guidance, approvals, or sandbox guidance
    • explicit empty-tool sessions must preserve truthful non-tool context such as workspace notes and TTS hints
    • prompt reports for explicit empty-tool sessions must keep tools.entries = [] and tools.listChars === 0
    • omitted toolNames must preserve legacy/default prompt behavior
    • explicit non-empty filtered tool lists must stay close to upstream/main behavior
  • Why this is the smallest reliable guardrail: The bug lives in prompt construction/reporting, so targeted unit tests exercise the exact faulty branches and distinguish the important runtime states without requiring unrelated runtime setup.
  • Existing test that already covers this (if any): None previously separated omitted tools from explicit empty effective tools, and none locked in later-section consistency for the no-tool path.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Explicit no-tool sessions now behave like true no-tool sessions:

  • no fallback tool inventory
  • no later tool-only guidance such as tool-call style, approval flow, subagent orchestration, tool-only messaging, or sandbox execution hints
  • prompt report correctly shows tools.entries = [] with tools.listChars === 0
  • workspace notes and TTS hints can still appear when supplied because they are context, not tool availability

Unspecified tool lists and explicit non-empty filtered tool lists keep the existing upstream/main-style prompt behavior.

Diagram (if applicable)

Before:
[toolNames omitted or []] -> [shared fallback/unconditional prompt sections] -> [tool inventory/tool-only guidance may appear]

After:
[toolNames omitted] -> [legacy/default prompt behavior]
[toolNames non-empty] -> [upstream/main-style filtered prompt behavior]
[toolNames = []] -> [explicit no-tools branch] -> [truthful no-tool prompt + tools.listChars = 0]

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Linux (Debian bookworm host)
  • Runtime/container: local source checkout run via pnpm; not a containerized repro
  • Model/provider: openai-codex/gpt-5.3-codex
  • Integration/channel (if any): CLI via openclaw agent --local
  • Relevant config (redacted): temporary no-tools agent with tools: { deny: ["*"] }

Steps

  1. Create a temporary no-tool agent in the deployment config so the runtime has a truly empty effective tool set:

    export OPENCLAW_CONFIG_PATH=/path/to/openclaw.json
    export OPENCLAW_STATE_DIR=/path/to/state-dir
    
    cp "$OPENCLAW_CONFIG_PATH" "$OPENCLAW_CONFIG_PATH.bak"
    python3 - <<'PY'
    import json, os
    p = os.environ["OPENCLAW_CONFIG_PATH"]
    with open(p) as f:
        cfg = json.load(f)
    agents = cfg.setdefault("agents", {}).setdefault("list", [])
    agents = [a for a in agents if a.get("id") != "no-tools"]
    agents.append({
        "id": "no-tools",
        "tools": {"deny": ["*"]},
    })
    cfg["agents"]["list"] = agents
    with open(p, "w") as f:
        json.dump(cfg, f, indent=2)
        f.write("\n")
    PY
  2. Run the normal CLI repro against the isolated deployment:

    openclaw agent --local \
      --agent no-tools \
      --session-id explicit-empty-tools-repro \
      --json \
      --message '/context detail' \
      --timeout 120 \
    | jq '{payloads, tools: .meta.systemPromptReport.tools, systemPrompt: .meta.systemPromptReport.systemPrompt}'

Expected

  • For a real explicit no-tool session:
    • tools.entries should be []
    • tools.listChars should be 0
    • the prompt/report should not imply standard tools, subagents, approvals, sandbox execution, or other tool-only capabilities

Actual

  • On the pre-fix source checkout, the bug reproduces:
    • tools.entries = []
    • tools.listChars = 1030
    • systemPrompt.chars = 26713

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Evidence highlights:

  • Live deployed-instance repro on pre-fix source checkout:
    • tools.entries = []
    • tools.listChars = 1030
    • systemPrompt.chars = 26713
  • Live deployed-instance repro on this fixed branch:
    • tools.entries = []
    • tools.listChars = 0
    • systemPrompt.chars = 16533
  • Targeted prompt/report tests on this branch:
    • src/agents/system-prompt.test.ts
    • src/agents/system-prompt-report.test.ts
    • result: 63 tests passed across 2 files

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • reran the deployed-instance repro on an isolated OpenClaw instance using the same gateway/state path and temporary no-tools config
    • confirmed the bug still reproduces on a pre-fix source checkout
    • confirmed the same repro no longer fails on this fixed branch
    • confirmed the verification signal is prompt/report metadata, not model-specific assistant output
  • Edge cases checked:
    • explicit empty-tool session keeps tools.entries = [] while dropping tools.listChars to 0
    • explicit empty-tool session no longer carries fallback tool inventory or later tool-only guidance
  • What you did not verify:
    • full OpenClaw test suite
    • unrelated model-level or workflow-level regressions outside this prompt-construction/reporting bug
    • performance impact beyond the prompt/report character-count change above

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: callers that accidentally relied on fallback tool inventory or guidance for explicit empty-tool sessions will now get a true no-tool prompt.
    • Mitigation: that behavior now matches explicit runtime intent, and regression tests lock in the distinction.
  • Risk: future prompt edits could blur the difference between omitted, explicit empty, and explicit non-empty filtered tool states.
    • Mitigation: regression tests now cover all three cases.

Changed files

  • src/agents/system-prompt-report.test.ts (modified, +211/-0)
  • src/agents/system-prompt-report.ts (modified, +34/-2)
  • src/agents/system-prompt.test.ts (modified, +215/-5)
  • src/agents/system-prompt.ts (modified, +171/-134)

Code Example

{
  "tools": {
    "strictInheritance": true
  }
}
RAW_BUFFERClick to expand / collapse

Summary

OpenClaw currently allows configuring tool permissions at the agent level (for example agents.list[].tools.profile = "full" or explicit allow/deny rules), but in practice the tools available to a concrete runtime session can still be reduced by subagent/session/runtime-specific behavior.

This creates a mismatch between:

  • declared agent tool permissions
  • effective session tool permissions

The result is that an agent may appear to have exec in its declared tool set, but a real session can still say it cannot execute commands.

This is not just a documentation issue — it creates operational uncertainty and breaks the expectation that agent tool policy is authoritative.


What I expected

If I define tool permissions for an agent, I expect one of the following to be true:

  1. Strict mode: actual session tools must match the agent’s declared tool policy unless explicitly overridden by a clearly visible config layer.
  2. If runtime clipping is still applied, OpenClaw should provide:
    • effective tool visibility
    • clipping source visibility
    • deterministic reasoning about why a tool is unavailable in that session

What currently happens

An agent can be configured with tools.profile: "full" and no explicit deny for exec, but a specific session may still be unable to use exec.

From operator perspective, this is extremely confusing because:

  • config says the agent has the tool
  • docs say runtime layers may still reduce tools
  • session does not clearly expose the final resolved/effective tool set
  • failure messages often do not explain which layer removed or blocked the tool

Why this is a real product problem

This is not only about safety policy — runtime clipping may be intentional — but the current behavior has three product gaps:

1. No strict inheritance mode

There appears to be no official config option to enforce:

declared agent tools == effective session tools

2. No effective capabilities visibility

There is no easy built-in way to inspect, for a given session:

  • declared tools
  • effective tools
  • removed tools
  • source of removal

3. No clipping reason trace

When a tool like exec is unavailable, the system should be able to say whether that is due to:

  • subagent policy
  • sandbox policy
  • runtime type
  • host unavailability
  • approval/security policy
  • ACP non-interactive restriction
  • internal session type limitations

Instead, operators often only see a generic “cannot execute in current session” type of outcome.


Requested features

A. Strict tool inheritance mode

Please add an official config option to enforce strict inheritance, for example something conceptually like:

{
  "tools": {
    "strictInheritance": true
  }
}

Meaning:

  • effective session tools should equal declared agent tools
  • unless there is an explicit, visible override layer
  • no silent runtime clipping

B. Effective tool visibility

Please expose, per session:

  • declared tools
  • effective tools
  • removed/blocked tools
  • removal source

This could be shown in:

  • session inspection APIs
  • control UI
  • logs
  • /status / debug tooling

C. Tool unavailability explanation

When a tool call is blocked or unavailable, return structured reason metadata such as:

  • blockedBy: subagent-policy
  • blockedBy: sandbox-policy
  • blockedBy: runtime-host-resolution
  • blockedBy: exec-approval-policy
  • blockedBy: acp-noninteractive-permissions

instead of a generic denial only.


Why this matters

For multi-agent workflows, operators need tool behavior to be:

  • deterministic
  • inspectable
  • explainable

Without that, agent-level tool configuration is misleading because it looks authoritative but is not actually final.


Suggested design principle

OpenClaw should clearly separate:

  • Declared capabilities: what the agent config says
  • Effective capabilities: what the current session actually has

And ideally provide an option to force parity between the two.


Environment

  • OpenClaw local gateway
  • multi-agent setup
  • agents configured with tools.profile: "full"
  • observed mismatch between declared tool permissions and real session execution capability

extent analysis

Fix Plan

To address the issue, we will implement the following changes:

  • Add a strictInheritance option to the tools configuration
  • Expose effective tool visibility per session
  • Provide structured reason metadata for tool unavailability

Code Changes

// Add strictInheritance option to tools configuration
{
  "tools": {
    "strictInheritance": true,
    "profile": "full"
  }
}
# Expose effective tool visibility per session
def get_session_tools(session_id):
    declared_tools = get_declared_tools(session_id)
    effective_tools = get_effective_tools(session_id)
    removed_tools = get_removed_tools(session_id)
    return {
        "declared_tools": declared_tools,
        "effective_tools": effective_tools,
        "removed_tools": removed_tools
    }

def get_declared_tools(session_id):
    # Return declared tools for the session
    pass

def get_effective_tools(session_id):
    # Return effective tools for the session
    pass

def get_removed_tools(session_id):
    # Return removed tools for the session
    pass
# Provide structured reason metadata for tool unavailability
def get_tool_unavailability_reason(session_id, tool_name):
    # Return reason metadata for tool unavailability
    reasons = {
        "subagent-policy": "Subagent policy blocked the tool",
        "sandbox-policy": "Sandbox policy blocked the tool",
        "runtime-host-resolution": "Runtime host resolution blocked the tool",
        "exec-approval-policy": "Exec approval policy blocked the tool",
        "acp-noninteractive-permissions": "ACP non-interactive permissions blocked the tool"
    }
    reason = get_tool_block_reason(session_id, tool_name)
    return {"blockedBy": reason, "message": reasons.get(reason, "Unknown reason")}

def get_tool_block_reason(session_id, tool_name):
    # Return reason for tool blockage
    pass

Verification

To verify the fix, test the following scenarios:

  • Configure an agent with tools.profile: "full" and strictInheritance: true
  • Verify that the effective tools for the session match the declared tools
  • Test tool unavailability and verify that the reason metadata is returned correctly

Extra Tips

  • Ensure that the strictInheritance option is properly documented and exposed in the configuration API
  • Consider adding additional logging and monitoring to track tool usage and blockage reasons
  • Review the implementation to ensure that it aligns with the suggested design principle of separating declared and effective capabilities.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Agent declared tools != effective session tools: no strict inheritance mode, no visibility, no clipping reason trace [1 pull requests, 1 participants]