openclaw - ✅(Solved) Fix Bug: scheduled cron agentTurn appends bundled MCP tools after toolsAllow filtering, wasting ~370k input tokens/day [3 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#70939Fetched 2026-04-24 10:37:35
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×3

Scheduled cron agentTurn runs do pass toolsAllow, but the allowlist is only applied to the base OpenClaw tool registry. Bundled MCP/LSP tools are materialized later and appended after policy filtering without reapplying toolsAllow, which allows an exec-only cron to compile with exec + 45 Atlassian tools in scheduled runs.

In our current fleet this is wasting roughly ~370k input tokens/day on average across live allowlisted cron jobs.

Root Cause

toolsAllow is being read at the right point for the base OpenClaw tool registry, but too early in the overall pipeline.

The bundled MCP/LSP tools are created later and appended into effectiveTools without any second allowlist pass.

So this is not:

  • “the scheduler ignores toolsAllow entirely”
  • nor “all 46 tools are built first and never filtered”

It is:

  • base tools filtered correctly
  • bundled tools appended later without allowlist filtering

Fix Action

Fix / Workaround

Minimal patch:

PR fix notes

PR #70958: fix(cron): apply toolsAllow filter to bundled MCP/LSP tools in scheduled runs

Description (problem / solution / changelog)

Summary

Scheduled cron agentTurn runs correctly applied toolsAllow to the base OpenClaw tool registry, but bundled MCP/LSP tools were materialized later and appended to effectiveTools without reapplying the allowlist. This caused exec-only cron jobs to still compile with e.g. 45 Atlassian MCP tools, wasting ~370k input tokens/day across affected deployments.

Root Cause

In src/agents/pi-embedded-runner/run/attempt.ts:

  1. Line ~570: applyEmbeddedAttemptToolsAllow(allTools, params.toolsAllow) filters base tools ✅
  2. Line ~710: applyFinalEffectiveToolPolicy(...) filters bundled tools by sandbox/policy ✅
  3. Line ~731: const effectiveTools = [...tools, ...filteredBundledTools] — appends bundled tools without re-checking toolsAllow

Fix

Apply applyEmbeddedAttemptToolsAllow() to the final combined tool array so toolsAllow is authoritative over all tool sources:

const effectiveTools = applyEmbeddedAttemptToolsAllow(
  [...tools, ...filteredBundledTools],
  params.toolsAllow,
);

When toolsAllow is empty/undefined, the function is a no-op (returns the full array unchanged).

Impact

  • Cron jobs with toolsAllow: [\"exec\"] will now correctly compile with only exec
  • Saves ~370k input tokens/day for affected deployments (~2.58M/week)
  • No behavioral change for cron jobs without toolsAllow

Testing

  • All 114 existing tests in attempt.test.ts pass ✅
  • The existing applyEmbeddedAttemptToolsAllow unit test confirms the filter behavior

Fixes #70939

Changed files

  • src/agents/pi-embedded-runner/run/attempt.ts (modified, +4/-1)

PR #70971: fix(cron): apply toolsAllow filter to bundled MCP/LSP tools in scheduled runs

Description (problem / solution / changelog)

Summary

Scheduled cron agentTurn runs correctly applied toolsAllow to the base OpenClaw tool registry, but bundled MCP/LSP tools were materialized later and appended to effectiveTools without reapplying the allowlist. This caused exec-only cron jobs to still compile with e.g. 45 Atlassian MCP tools, wasting ~370k input tokens/day across affected deployments.

Resubmission of #70958 which was auto-closed by the too-many-prs bot (open PR count now reduced).

Root Cause

In src/agents/pi-embedded-runner/run/attempt.ts:

  1. Line ~570: applyEmbeddedAttemptToolsAllow(allTools, params.toolsAllow) filters base tools ✅
  2. Line ~710: applyFinalEffectiveToolPolicy(...) filters bundled tools by sandbox/policy ✅
  3. Line ~731: const effectiveTools = [...tools, ...filteredBundledTools] — appends bundled tools without re-checking toolsAllow

Fix

Apply applyEmbeddedAttemptToolsAllow() to the final combined tool array so toolsAllow is authoritative over all tool sources:

const effectiveTools = applyEmbeddedAttemptToolsAllow(
  [...tools, ...filteredBundledTools],
  params.toolsAllow,
);

When toolsAllow is empty/undefined, the function is a no-op (returns the full array unchanged).

Impact

  • Cron jobs with toolsAllow: ["exec"] will now correctly compile with only exec
  • Saves ~370k input tokens/day for affected deployments (~2.58M/week)
  • No behavioral change for cron jobs without toolsAllow

Testing

  • All 114 existing tests in attempt.test.ts pass ✅
  • The existing applyEmbeddedAttemptToolsAllow unit test confirms the filter behavior

Fixes #70939

Changed files

  • src/agents/pi-embedded-runner/run/attempt.ts (modified, +4/-1)

PR #71034: fix: apply toolsAllow filter to bundled MCP/LSP tools

Description (problem / solution / changelog)

Summary

Fixes #70939

Scheduled cron agentTurn runs pass toolsAllow to restrict tool access, but the allowlist was only applied to the base tool registry. Bundled MCP/LSP tools were materialized later and appended to the effective tool list without reapplying toolsAllow.

Impact

An exec-only cron could compile with exec + 45 Atlassian MCP tools, wasting ~370k input tokens/day across allowlisted cron jobs.

Fix

Wrap filteredBundledTools with applyEmbeddedAttemptToolsAllow() before merging into effectiveTools in attempt.ts:739, so bundled MCP/LSP tools are also subject to the toolsAllow policy.

Change

- const effectiveTools = [...tools, ...filteredBundledTools];
+ const effectiveTools = [
+   ...tools,
+   ...applyEmbeddedAttemptToolsAllow(filteredBundledTools, params.toolsAllow),
+ ];

Changed files

  • src/agents/pi-embedded-runner/run/attempt.ts (modified, +4/-1)

Code Example

"toolsAllow": ["exec"]

---

{
  "timeoutMs": 120000,
  "trigger": "cron",
  "disableTools": false,
  "toolResultFormat": "markdown",
  "toolsAllow": ["exec"]
}

---

{
  "timeoutMs": 120000,
  "trigger": "cron",
  "disableTools": false,
  "toolResultFormat": "markdown",
  "toolsAllow": ["exec"]
}

---

const allowSet = new Set(params.toolsAllow ?? [])
const effectiveTools = params.toolsAllow?.length
  ? [...tools, ...filteredBundledTools].filter(tool => allowSet.has(tool.name))
  : [...tools, ...filteredBundledTools]
RAW_BUFFERClick to expand / collapse

Summary

Scheduled cron agentTurn runs do pass toolsAllow, but the allowlist is only applied to the base OpenClaw tool registry. Bundled MCP/LSP tools are materialized later and appended after policy filtering without reapplying toolsAllow, which allows an exec-only cron to compile with exec + 45 Atlassian tools in scheduled runs.

In our current fleet this is wasting roughly ~370k input tokens/day on average across live allowlisted cron jobs.

Impact

Current observed fleet impact:

  • 11 enabled allowlisted agentTurn cron jobs
  • estimated waste: ~2.58M extra input tokens/week
  • estimated average: ~368k/day
  • confirmed floor from just two observed leaking jobs: ~326k/day

This is a real cost/perf bug, not just a weird Work Inbox Review anecdote.

Expected behavior

If a cron payload sets:

"toolsAllow": ["exec"]

then the compiled context should only serialize exec (plus any other explicitly allowed tools), regardless of whether bundled MCP/LSP tools are available in the runtime.

Actual behavior

For scheduled cron agentTurn runs:

  • toolsAllow is passed into the embedded attempt path
  • the base tool registry is filtered correctly
  • bundled MCP/LSP tools are materialized later
  • those bundled tools are appended to the final effective tool list without reapplying toolsAllow

Result: scheduled exec-only runs can still serialize exec + 45 Atlassian MCP tools.

Repro evidence

A. Manual validation run (correct behavior)

Trajectory session: f36ec82e-ada6-4dea-9906-b8e25d7d852c

Runtime config:

{
  "timeoutMs": 120000,
  "trigger": "cron",
  "disableTools": false,
  "toolResultFormat": "markdown",
  "toolsAllow": ["exec"]
}

Observed:

  • compiled tools: ['exec']
  • tool schema chars: 1155
  • system prompt chars: 40239

B. Scheduled cron run (buggy behavior)

Trajectory session: 5d1beac3-1b5e-4881-a6a1-6d23561df888

Same runtime config:

{
  "timeoutMs": 120000,
  "trigger": "cron",
  "disableTools": false,
  "toolResultFormat": "markdown",
  "toolsAllow": ["exec"]
}

Observed:

  • compiled tools: exec + 45 Atlassian MCP tools
  • tool schema chars: 45116
  • system prompt chars: 42227

C. Second confirmed leaking allowlisted cron

Trajectory session: 6d8a7260-4351-45a2-ab78-76e891df200e

Observed:

  • same leak shape: exec + 45 Atlassian MCP tools
  • tool schema chars: 45116
  • system prompt chars: 42230

D. Control: scheduled cron without toolsAllow

Trajectory session: e39c58d1-7fc1-416c-a112-72559e5f6182 (Daily Zight Review)

Observed:

  • no toolsAllow
  • compiled tool count: 20
  • tool schema chars: 16169
  • system prompt chars: 50582

This suggests the bug is not “all scheduled agentTurn runs load 46 tools.” It affects runs where toolsAllow narrows the base registry, but bundled tools are appended afterward.

Traced call path

dist/index.js is just the launcher. The scheduled cron path resolves to:

  • dist/index.js
  • server.impl-D40kmTX8.js:3173 calls executeCronRun(...)
  • run-executor.runtime-r9xOO-zi.js:102-138 calls runEmbeddedPiAgent(...) with toolsAllow: params.agentPayload?.toolsAllow
  • pi-embedded-runner-BFOhIiuh.js:28/883/1297
  • selection-DGLE6AvW.js

context.compiled is recorded at:

  • selection-DGLE6AvW.js:6925

Relevant tool assembly lines:

  • selection-DGLE6AvW.js:5693applyEmbeddedAttemptToolsAllow(tools, toolsAllow)
  • selection-DGLE6AvW.js:5749 — base registry filtered via applyEmbeddedAttemptToolsAllow(createOpenClawCodingTools(...), params.toolsAllow)
  • selection-DGLE6AvW.js:5900materializeBundleMcpToolsForRun(...)
  • selection-DGLE6AvW.js:5913applyFinalEffectiveToolPolicy(...)
  • selection-DGLE6AvW.js:5934const effectiveTools = [...tools, ...filteredBundledTools];

Root cause

toolsAllow is being read at the right point for the base OpenClaw tool registry, but too early in the overall pipeline.

The bundled MCP/LSP tools are created later and appended into effectiveTools without any second allowlist pass.

So this is not:

  • “the scheduler ignores toolsAllow entirely”
  • nor “all 46 tools are built first and never filtered”

It is:

  • base tools filtered correctly
  • bundled tools appended later without allowlist filtering

Suggested fix

Minimal patch:

  • reapply toolsAllow after bundled MCP/LSP tool materialization, before finalizing effectiveTools

Likely safest places:

  1. filter filteredBundledTools by the same allowlist set before appending, or
  2. apply the allowlist again to the final effectiveTools

Conceptually:

const allowSet = new Set(params.toolsAllow ?? [])
const effectiveTools = params.toolsAllow?.length
  ? [...tools, ...filteredBundledTools].filter(tool => allowSet.has(tool.name))
  : [...tools, ...filteredBundledTools]

Why this matters

This bug silently increases prompt size, latency, and cost for thin scheduled cron jobs that are explicitly configured to use a tiny toolset.

The operator thinks they have an exec-only cron, but the scheduled run can still serialize a large bundled MCP catalog into context.compiled.

extent analysis

TL;DR

Reapply the toolsAllow filter after materializing bundled MCP/LSP tools to ensure only allowed tools are included in the final effectiveTools list.

Guidance

  • Identify the point in the code where effectiveTools is finalized and apply the toolsAllow filter again to ensure bundled tools are filtered correctly.
  • Consider filtering filteredBundledTools by the toolsAllow set before appending to effectiveTools for better performance.
  • Review the selection-DGLE6AvW.js file, specifically lines 5693, 5749, 5900, 5913, and 5934, to understand the current tool assembly and filtering process.
  • Test the fix by running scheduled cron jobs with toolsAllow set to a specific tool, such as exec, and verify that only the allowed tools are included in the context.compiled output.

Example

const allowSet = new Set(params.toolsAllow ?? [])
const effectiveTools = params.toolsAllow?.length
  ? [...tools, ...filteredBundledTools].filter(tool => allowSet.has(tool.name))
  : [...tools, ...filteredBundledTools]

Notes

The suggested fix assumes that the toolsAllow filter is correctly applied to the base OpenClaw tool registry. Additional testing may be necessary to ensure the fix does not introduce any unintended behavior.

Recommendation

Apply the workaround by reapplying the toolsAllow filter after materializing bundled MCP/LSP tools, as this will ensure that only allowed tools are included in the final effectiveTools list and prevent unnecessary costs and latency.

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…

FAQ

Expected behavior

If a cron payload sets:

"toolsAllow": ["exec"]

then the compiled context should only serialize exec (plus any other explicitly allowed tools), regardless of whether bundled MCP/LSP tools are available in the runtime.

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 Bug: scheduled cron agentTurn appends bundled MCP tools after toolsAllow filtering, wasting ~370k input tokens/day [3 pull requests, 1 participants]