openclaw - ✅(Solved) Fix LLM-only modelRun fails when inherited tool allowlist is non-empty [3 pull requests, 2 comments, 3 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#74810Fetched 2026-05-01 05:41:06
View on GitHub
Comments
2
Participants
3
Timeline
6
Reactions
2
Timeline (top)
cross-referenced ×4commented ×2

LLM-only runs that intentionally disable tools can fail before reaching the provider when the runtime inherits a global tool allowlist. The error says no callable tools remain, even though tools are intentionally disabled for the run.

This is related to #74019, but this issue tracks the generic one-shot modelRun path in addition to plugin-owned llm-task runs.

Error Message

GatewayClientRequestError: Error: No callable tools remain after resolving explicit tool allowlist (tools.allow: *, lobster, llm-task); tools are disabled for this run. Fix the allowlist or enable the plugin that registers the requested tool.

Root Cause

llm-task and one-shot model probes are intentionally no-tool execution paths. A global infrastructure tool allowlist such as tools.alsoAllow: ["lobster", "llm-task"] should not break no-tool model runs.

Fix Action

Fix / Workaround

Before local patching, the command failed before reaching the provider.

Local workaround validated

Local package patch made disabled-tool runs skip this empty allowlist error and clear runtime allowlist for LLM-only calls:

After patching and restarting:

PR fix notes

PR #74834: fix: 5 targeted bug fixes (gemini-cli, plugin commands, openrouter thinking, cron summary, LLM-only tools)

Description (problem / solution / changelog)

Summary

Five independent targeted fixes, each addressing a distinct bug:

1. fix(google): remove unsupported --skip-trust flag from gemini-cli backend (#74749)

gemini-cli >= 0.38.x dropped --skip-trust. Passing it causes yargs to exit non-zero on every call, resulting in 100% failure rate for the gemini CLI backend. Removed from both args and resumeArgs.

2. fix(plugins): guard against undefined plugin command handler result (#74800)

Plugin command handlers typed as Promise<PluginCommandResult> can return undefined at runtime. The caller in commands-plugin.ts immediately destructures result.continueAgent, crashing with Cannot read properties of undefined. Added a ?? {} fallback at the execution boundary.

3. fix(openrouter): add resolveThinkingProfile with xhigh for reasoning models (#74788)

OpenRouter had no resolveThinkingProfile, so /think xhigh was rejected for all models including openrouter/deepseek/deepseek-v4-pro. Added a profile that exposes xhigh for models with reasoning: true (unless explicitly excluded via isOpenRouterProxyReasoningUnsupportedModel).

4. fix(cron): prefer finalAssistantVisibleText over raw error text in task summary (#74807)

When a cron agentTurn run had only error payloads (tool call failed, agent self-corrected but never sent an explicit reply via messaging tool), fallbackSummary fell through to pickSummaryFromOutput(firstText) where firstText was the tool error. Now prefers finalAssistantVisibleText as an intermediate fallback before resorting to raw payload text.

5. fix(agents): skip tool allowlist guard for LLM-only runs (#74810)

buildEmptyExplicitToolAllowlistError raised an error when callableToolNames was empty, even when disableTools: true. LLM-only runs intentionally disable tools, so an empty callable set is expected. Added early return when disableTools === true.

Testing

Each fix is independently testable:

  • #74749: pnpm test -- src/agents/cli-backends.test.ts
  • #74800: Trigger any plugin command whose handler returns undefined
  • #74788: pnpm test -- src/agents/pi-embedded-runner/openrouter-model-capabilities.test.ts
  • #74807: pnpm test -- src/cron/isolated-agent
  • #74810: pnpm test -- src/agents/tool-allowlist-guard

🤖 Generated with Claude Code

Changed files

  • extensions/google/cli-backend.ts (modified, +2/-10)
  • extensions/openrouter/index.ts (modified, +18/-0)
  • src/agents/cli-backends.test.ts (modified, +2/-10)
  • src/agents/tool-allowlist-guard.ts (modified, +4/-0)
  • src/cron/isolated-agent/helpers.ts (modified, +10/-2)
  • src/plugins/commands.ts (modified, +1/-1)

PR #74841: fix(agents): skip tool allowlist empty guard for LLM-only runs

Description (problem / solution / changelog)

Summary

Targeted single-fix PR.

Closes #74810

🤖 Generated with Claude Code

Changed files

  • src/agents/tool-allowlist-guard.ts (modified, +4/-0)

PR #73031: fix(agents): skip empty allowlist guard for modelRun

Description (problem / solution / changelog)

Summary

openclaw infer model run aborts before any provider request when ~/.openclaw/openclaw.json has a non-empty tools.alsoAllow, because the empty explicit-allowlist guard still evaluates the global tool policy even though modelRun:true intentionally zeroes toolsRaw.

The fix adds a modelRun?: boolean parameter to buildEmptyExplicitToolAllowlistError and short-circuits to null when it is true. The embedded attempt path (attempt.ts) now passes modelRun: params.modelRun into the guard so model-only call shapes can run text-only without failing on a global tools.alsoAllow.

This keeps the existing fail-closed behavior for runtime disableTools conflicts and for ordinary agent runs whose explicit allowlist resolves to no callable tools (the original intent of #71292).

Repro

  1. Set in ~/.openclaw/openclaw.json:

    "tools": { "profile": "coding", "alsoAllow": ["lobster", "llm-task"] }
  2. Run:

    openclaw infer model run --local --model openai-codex/gpt-5.5 --prompt 'reply exactly: ok' --json

    Before this PR, fails with No callable tools remain after resolving explicit tool allowlist (...). After this PR, the model is reached.

Test added

src/agents/tool-allowlist-guard.test.ts — new case skips the empty-allowlist guard for modelRun call shapes. Asserts buildEmptyExplicitToolAllowlistError returns null when modelRun: true even with non-empty sources and zero callable tools. The existing disableTools: true fail-closed test is unchanged.

Validation

  • pnpm test src/agents/tool-allowlist-guard.test.ts — 7/7 passing
  • pnpm check:changed --staged — green (typecheck core/test, lint, import cycles, conflict markers, changelog attributions, etc.)
  • pnpm test:changed — 2521 passing; one unrelated pre-existing failure on src/agents/tools/cron-tool.schema.test.ts (job.delivery keys assertion is missing the new threadId introduced by upstream b6be422306 fix(cron): accept threaded delivery in gateway schema). Reproduces on bare upstream/main with my changes stashed, so it is not blocking.

Closes

Fixes #73024

AI assistance

This PR was prepared with Claude Code (Opus 4.7). Lightly tested locally — the unit test covers the guard contract; the end-to-end infer model run repro was not exercised live since it requires a live model account.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/agents/pi-embedded-runner/run/attempt.ts (modified, +1/-0)
  • src/agents/tool-allowlist-guard.test.ts (modified, +12/-0)
  • src/agents/tool-allowlist-guard.ts (modified, +7/-0)

Code Example

"tools": {
  "profile": "full",
  "alsoAllow": ["lobster", "llm-task"]
}

---

openclaw infer model run --gateway \
  --model 'google/gemini-3.1-flash-lite-preview' \
  --prompt 'Return exactly: pong'

---

GatewayClientRequestError: Error: No callable tools remain after resolving explicit tool allowlist (tools.allow: *, lobster, llm-task); tools are disabled for this run. Fix the allowlist or enable the plugin that registers the requested tool.

---

openclaw infer model run --gateway \
  --model 'openrouter/google/gemini-3.1-flash-lite-preview' \
  --prompt 'Return exactly: pong'

---

openclaw infer model run --gateway \
  --model 'google/gemini-3.1-flash-lite-preview' \
  --prompt 'Return exactly: pong'

---

pong

---

403 Key limit exceeded (monthly limit)
RAW_BUFFERClick to expand / collapse

Bug type

Tool policy / model-run bug

Version

OpenClaw 2026.4.27 (cbc2ba0)

Summary

LLM-only runs that intentionally disable tools can fail before reaching the provider when the runtime inherits a global tool allowlist. The error says no callable tools remain, even though tools are intentionally disabled for the run.

This is related to #74019, but this issue tracks the generic one-shot modelRun path in addition to plugin-owned llm-task runs.

Relevant config

The fleet has normal full tools plus plugin tool additions:

"tools": {
  "profile": "full",
  "alsoAllow": ["lobster", "llm-task"]
}

Both lobster and llm-task are enabled plugin entries.

Reproduction

Run a one-shot model turn through the gateway:

openclaw infer model run --gateway \
  --model 'google/gemini-3.1-flash-lite-preview' \
  --prompt 'Return exactly: pong'

Before local patching, the command failed before reaching the provider.

Observed behavior

GatewayClientRequestError: Error: No callable tools remain after resolving explicit tool allowlist (tools.allow: *, lobster, llm-task); tools are disabled for this run. Fix the allowlist or enable the plugin that registers the requested tool.

Same error appeared for OpenRouter one-shot runs:

openclaw infer model run --gateway \
  --model 'openrouter/google/gemini-3.1-flash-lite-preview' \
  --prompt 'Return exactly: pong'

Expected behavior

When a run is explicitly LLM-only (disableTools: true / modelRun: true), inherited tool allowlists should not cause an empty-callable-tools failure. It should proceed as a no-tool model call.

Root cause hypothesis

There are two paths with the same shape:

  1. llm-task plugin calls embedded runner with disableTools: true.
  2. Generic one-shot modelRun sets disableTools: true.

But the empty explicit allowlist guard still sees inherited effective allowlist entries from tools.profile / tools.alsoAllow, then errors because there are zero callable tools.

Local workaround validated

Local package patch made disabled-tool runs skip this empty allowlist error and clear runtime allowlist for LLM-only calls:

  • extensions/llm-task/index.js: pass toolsAllow: [] next to disableTools: true.
  • attempt-execution-*.js: for modelRun === true, pass toolsAllow: [] instead of inheriting params.opts.toolsAllow.
  • selection-*.js: in buildEmptyExplicitToolAllowlistError, return null when params.disableTools === true.

After patching and restarting:

openclaw infer model run --gateway \
  --model 'google/gemini-3.1-flash-lite-preview' \
  --prompt 'Return exactly: pong'

returns:

pong

OpenRouter also reaches the provider after this fix; in our environment it then fails only on an existing account quota issue:

403 Key limit exceeded (monthly limit)

Why this matters

llm-task and one-shot model probes are intentionally no-tool execution paths. A global infrastructure tool allowlist such as tools.alsoAllow: ["lobster", "llm-task"] should not break no-tool model runs.

extent analysis

TL;DR

To fix the issue, pass an empty toolsAllow list when disableTools is true to prevent the empty allowlist error.

Guidance

  • Identify areas where disableTools: true is set, such as in the llm-task plugin or generic one-shot modelRun, and ensure an empty toolsAllow list is passed.
  • Modify the attempt-execution-*.js file to pass toolsAllow: [] when modelRun === true, instead of inheriting params.opts.toolsAllow.
  • Update the selection-*.js file to return null in buildEmptyExplicitToolAllowlistError when params.disableTools === true, allowing the run to proceed without checking the allowlist.
  • Verify the fix by running a one-shot model turn through the gateway with disableTools: true and checking that it reaches the provider without errors.

Example

// In extensions/llm-task/index.js
// Pass toolsAllow: [] next to disableTools: true
const params = {
  disableTools: true,
  toolsAllow: []
};

// In attempt-execution-*.js
if (modelRun === true) {
  params.toolsAllow = []; // Pass an empty toolsAllow list
}

Notes

This fix assumes that the issue is caused by the inherited tool allowlist interfering with LLM-only runs. If the issue persists, further investigation may be needed to identify other potential causes.

Recommendation

Apply the workaround by passing an empty toolsAllow list when disableTools is true, as this has been validated to fix the issue in the local environment.

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

When a run is explicitly LLM-only (disableTools: true / modelRun: true), inherited tool allowlists should not cause an empty-callable-tools failure. It should proceed as a no-tool model call.

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 LLM-only modelRun fails when inherited tool allowlist is non-empty [3 pull requests, 2 comments, 3 participants]