openclaw - 💡(How to fix) Fix `resolvePinnedAgentHarnessPolicy` hardcodes `fallback: "none"` — config setting is silently ignored, and the safety brake is the only thing preventing silent extra-usage billing [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#73096Fetched 2026-04-28 06:27:34
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0
Author
Participants

Error Message

So the fix needs to balance: configurability (honor the user's stated intent) vs. safety (warn loudly when the intent leads to a not-obviously-billed code path). 4. Observe: error message says PI fallback is disabled regardless of config

Root Cause

dist/selection-C3otDzGD.js:7665–7672:

function resolvePinnedAgentHarnessPolicy(agentHarnessId) {
    if (!agentHarnessId?.trim()) return;
    const runtime = normalizeEmbeddedAgentRuntime(agentHarnessId);
    if (runtime === "auto") return;
    return {
        runtime,
        fallback: "none"   // ← hardcoded
    };
}

Called by selectAgentHarnessDecision at line 7533 — params.config is in scope at the call site, so the configured fallback IS available, just never passed through.

The unpinned path (resolveAgentHarnessPolicy at line 7692) correctly reads params.config?.agents?.defaults?.embeddedHarness?.fallback via resolveAgentHarnessFallbackPolicy. The asymmetry is the bug.

Fix Action

Fix / Workaround

Setting agents.defaults.embeddedHarness.fallback: "pi" in openclaw.json does not change the behavior of any code path that goes through the pinned harness policy (subagent execution, channel boot, model-fallback chain dispatch when an agent harness id is pinned). The configured fallback is read by the unpinned policy path only.

This creates a surprising gap between docs/schema (which present fallback as a configurable knob) and runtime behavior — and has direct billing implications for users on the OAuth/cliBackend setup, because the silent path through PI dispatches direct API calls that consume Claude extra-usage credits.

When we patched our local copy to honor fallback: "pi", the symptom was real-world consequential:

Code Example

function resolvePinnedAgentHarnessPolicy(agentHarnessId) {
    if (!agentHarnessId?.trim()) return;
    const runtime = normalizeEmbeddedAgentRuntime(agentHarnessId);
    if (runtime === "auto") return;
    return {
        runtime,
        fallback: "none"   // ← hardcoded
    };
}

---

- function resolvePinnedAgentHarnessPolicy(agentHarnessId) {
+ function resolvePinnedAgentHarnessPolicy(agentHarnessId, configuredFallback) {
      if (!agentHarnessId?.trim()) return;
      const runtime = normalizeEmbeddedAgentRuntime(agentHarnessId);
      if (runtime === "auto") return;
      return {
          runtime,
-         fallback: "none"
+         fallback: configuredFallback ?? "none"
      };
  }

  // and at the call site (line 7533):
- const pinnedPolicy = resolvePinnedAgentHarnessPolicy(params.agentHarnessId);
+ const pinnedPolicy = resolvePinnedAgentHarnessPolicy(
+     params.agentHarnessId,
+     params.config?.agents?.defaults?.embeddedHarness?.fallback
+ );
RAW_BUFFERClick to expand / collapse

Affected version

OpenClaw 2026.4.24 (build cbcfdf6).

Symptom

Setting agents.defaults.embeddedHarness.fallback: "pi" in openclaw.json does not change the behavior of any code path that goes through the pinned harness policy (subagent execution, channel boot, model-fallback chain dispatch when an agent harness id is pinned). The configured fallback is read by the unpinned policy path only.

This creates a surprising gap between docs/schema (which present fallback as a configurable knob) and runtime behavior — and has direct billing implications for users on the OAuth/cliBackend setup, because the silent path through PI dispatches direct API calls that consume Claude extra-usage credits.

Root cause

dist/selection-C3otDzGD.js:7665–7672:

function resolvePinnedAgentHarnessPolicy(agentHarnessId) {
    if (!agentHarnessId?.trim()) return;
    const runtime = normalizeEmbeddedAgentRuntime(agentHarnessId);
    if (runtime === "auto") return;
    return {
        runtime,
        fallback: "none"   // ← hardcoded
    };
}

Called by selectAgentHarnessDecision at line 7533 — params.config is in scope at the call site, so the configured fallback IS available, just never passed through.

The unpinned path (resolveAgentHarnessPolicy at line 7692) correctly reads params.config?.agents?.defaults?.embeddedHarness?.fallback via resolveAgentHarnessFallbackPolicy. The asymmetry is the bug.

Tension worth flagging in this issue

The hardcoded "none" is also acting as a safety brake: when an explicit plugin-runtime claim is set (e.g., embeddedHarness.runtime: "claude-cli") but no plugin registers that harness id, the strict throw alerts the user instead of silently substituting a different (more expensive) backend.

When we patched our local copy to honor fallback: "pi", the symptom was real-world consequential:

  • Loud Requested agent harness "claude-cli" is not registered and PI fallback is disabled errors stopped firing (good)
  • Subagent execution, channel boot, and model-fallback chain dispatch silently switched to PI (which uses Anthropic direct-API)
  • Direct-API calls on an OAuth credential trigger Anthropic's "Third-party apps now draw from your extra usage, not your plan limits" filter — billing against extra-usage credits
  • Users who didn't notice the config implication were silently burning credits

So the fix needs to balance: configurability (honor the user's stated intent) vs. safety (warn loudly when the intent leads to a not-obviously-billed code path).

Suggested fix (one of)

Option A — honor configured fallback, document the billing risk loudly

- function resolvePinnedAgentHarnessPolicy(agentHarnessId) {
+ function resolvePinnedAgentHarnessPolicy(agentHarnessId, configuredFallback) {
      if (!agentHarnessId?.trim()) return;
      const runtime = normalizeEmbeddedAgentRuntime(agentHarnessId);
      if (runtime === "auto") return;
      return {
          runtime,
-         fallback: "none"
+         fallback: configuredFallback ?? "none"
      };
  }

  // and at the call site (line 7533):
- const pinnedPolicy = resolvePinnedAgentHarnessPolicy(params.agentHarnessId);
+ const pinnedPolicy = resolvePinnedAgentHarnessPolicy(
+     params.agentHarnessId,
+     params.config?.agents?.defaults?.embeddedHarness?.fallback
+ );

Pair with: a one-time gateway warning at boot when fallback: "pi" is configured AND the requested runtime isn't a registered plugin harness — to flag that "your subagent execution will go through direct-API and consume extra-usage credits."

Option B — keep pinned-policy strict, fix the docs/schema

If the hardcoded behavior is intentional, remove the implication that fallback is honored everywhere. The schema description for embeddedHarness.fallback currently says "Embedded harness fallback when no plugin harness matches. Auto mode defaults to pi; explicit plugin runtimes default to none and do not inherit broader fallback settings." — that text is technically correct, but easy to miss the "do not inherit broader fallback settings" caveat. Bolder warning recommended.

Repro

  1. Set agents.defaults.embeddedHarness: { runtime: "claude-cli", fallback: "pi" } (or any non-"auto" runtime)
  2. Restart gateway
  3. Trigger anything that hits harness lookup (channel boot, sessions_spawn, infer model run --gateway)
  4. Observe: error message says PI fallback is disabled regardless of config

Adjacent

  • #31049 — Agent fails to fallback when primary model API is unreachable (model-fallback layer, related but different)

Notes from one user's empirical investigation

In a workspace running this version, the claude-cli harness was never registered for the entire lifetime of the install (4 days), producing 547 harness errors in a single day (the loud-fail mode). The user's setup was generated by openclaw configure wizard, which writes embeddedHarness.runtime: "claude-cli" by default — but no stock plugin in the dist registers a harness with that id. Combined with the hardcoded fallback, this means the wizard configures every cliBackend-using install into a state that fails on every harness-lookup code path. Worth checking whether this fallback fix should be paired with either (a) shipping a stock claude-cli harness plugin that wraps cliBackends, or (b) having the wizard avoid writing the claude-cli runtime when no such plugin is registered.

extent analysis

TL;DR

The most likely fix is to modify the resolvePinnedAgentHarnessPolicy function to honor the configured fallback, while also documenting the potential billing risk.

Guidance

  • Modify the resolvePinnedAgentHarnessPolicy function to accept the configured fallback as an argument, and use it to determine the fallback behavior.
  • Update the call site to pass the configured fallback to the resolvePinnedAgentHarnessPolicy function.
  • Consider adding a one-time gateway warning at boot when fallback: "pi" is configured and the requested runtime isn't a registered plugin harness.
  • Review the documentation and schema to ensure that the behavior of the fallback setting is clearly described.

Example

- function resolvePinnedAgentHarnessPolicy(agentHarnessId) {
+ function resolvePinnedAgentHarnessPolicy(agentHarnessId, configuredFallback) {
      if (!agentHarnessId?.trim()) return;
      const runtime = normalizeEmbeddedAgentRuntime(agentHarnessId);
      if (runtime === "auto") return;
      return {
          runtime,
-         fallback: "none"
+         fallback: configuredFallback ?? "none"
      };
  }

  // and at the call site (line 7533):
- const pinnedPolicy = resolvePinnedAgentHarnessPolicy(params.agentHarnessId);
+ const pinnedPolicy = resolvePinnedAgentHarnessPolicy(
+     params.agentHarnessId,
+     params.config?.agents?.defaults?.embeddedHarness?.fallback
+ );

Notes

The fix needs to balance configurability with safety, to avoid silently switching to a more expensive backend. The suggested fix is to honor the configured fallback, while also documenting the potential billing risk.

Recommendation

Apply the workaround by modifying the resolvePinnedAgentHarnessPolicy function to honor the configured fallback, and document the potential billing risk. This will ensure that the behavior of the fallback setting is consistent with the user's configuration, while also warning them of

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 - 💡(How to fix) Fix `resolvePinnedAgentHarnessPolicy` hardcodes `fallback: "none"` — config setting is silently ignored, and the safety brake is the only thing preventing silent extra-usage billing [1 participants]