openclaw - ✅(Solved) Fix Exec tool: invalid `host:` values silently coerce to sandbox, causing agents to confabulate host-level facts [1 pull requests, 1 comments, 2 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#74426Fetched 2026-04-30 06:23:59
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
2
Timeline (top)
closed ×1commented ×1cross-referenced ×1

The exec tool's host argument is declared as a strict enum (auto | sandbox | gateway | node) in both the Zod schema and the TypeBox descriptor. When an agent passes an unrecognized string (e.g., host: "spark-ff13"), Zod's .optional() strips the invalid value silently, the runtime falls back to the default target, the command runs there, and the tool result returned to the model does not indicate that the requested target was overridden. The model believes the command ran where it asked, and reports pod-local facts as if they were host-level facts.

This produces a deterministic confabulation pattern in agents that wrap host-introspection tasks: the agent sees "no harvester process found" / "endpoint connection refused" from inside the sandbox, concludes the host's harvester is down, and starts trying to restart a service that is actually running fine. The model can't self-correct from the result alone — the response shape is identical to a successful host-targeted call.

Error Message

resolveExecTarget in the same file falls through to configuredTarget ?? "auto" when requestedTarget is null (which is what Zod produces from an invalid optional). No error, no warning surfaced. This makes the model see a clear error like "host": "spark-ff13" is not a valid value, allowed: auto|sandbox|gateway|node and self-correct on the next turn.

Root Cause

The exec tool's host argument is declared as a strict enum (auto | sandbox | gateway | node) in both the Zod schema and the TypeBox descriptor. When an agent passes an unrecognized string (e.g., host: "spark-ff13"), Zod's .optional() strips the invalid value silently, the runtime falls back to the default target, the command runs there, and the tool result returned to the model does not indicate that the requested target was overridden. The model believes the command ran where it asked, and reports pod-local facts as if they were host-level facts.

This produces a deterministic confabulation pattern in agents that wrap host-introspection tasks: the agent sees "no harvester process found" / "endpoint connection refused" from inside the sandbox, concludes the host's harvester is down, and starts trying to restart a service that is actually running fine. The model can't self-correct from the result alone — the response shape is identical to a successful host-targeted call.

Fix Action

Fixed

PR fix notes

PR #74468: fix(exec): reject invalid host targets

Description (problem / solution / changelog)

Summary

  • Problem: per-call exec host values outside auto, sandbox, gateway, and node were normalized away, so hostname-like input could silently fall back to the default target.
  • Why it matters: a malformed direct exec request should fail closed before any command launches instead of relying on fallback routing.
  • What changed: validate per-call exec host values before target resolution, tighten the exec tool schema enum, add regression coverage for invalid string and non-string host values, and document the allowed host set.
  • What did NOT change (scope boundary): no new config keys, no approval/elevated routing changes, and no change to default host=auto behavior.

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

  • Fixes #74426
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: direct exec calls used the permissive target normalizer, which returned null for unknown host values and allowed target resolution to fall back to the configured/default target.
  • Missing detection / guardrail: regression coverage did not assert that malformed per-call host input fails before command launch.
  • Contributing context (if known): N/A

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/infra/exec-approvals-policy.test.ts, src/agents/bash-tools.exec-foreground-failures.test.ts
  • Scenario the test should lock in: invalid string and non-string per-call host values throw before the exec command can launch.
  • Why this is the smallest reliable guardrail: one policy helper test locks the closed host set, and one exec tool test verifies runtime behavior at the command boundary.
  • Existing test that already covers this (if any): N/A
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Direct exec calls with malformed host values now fail with an explicit validation error instead of falling back to the configured/default exec target. Valid auto, sandbox, gateway, and node behavior is unchanged.

Diagram (if applicable)

Before:
exec({ command, host: "spark-ff13" }) -> invalid host normalizes to null -> default target runs command

After:
exec({ command, host: "spark-ff13" }) -> validation error -> command is not launched

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? Yes
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: command execution input validation is stricter; malformed per-call host values now fail closed before launch.

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node.js v24.14.0, pnpm
  • Model/provider: N/A
  • Integration/channel (if any): exec tool
  • Relevant config (redacted): N/A

Steps

  1. Call the exec tool with host: "spark-ff13" and a command.
  2. Call the exec tool with a non-string host value.

Expected

  • The exec tool rejects the malformed host before launching the command.

Actual

  • Before this change, invalid host-like values could be treated like no per-call host override and fall back to the default target.

Evidence

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

New regression tests cover invalid string and non-string host values, and the focused test lane passes.

Human Verification (required)

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

  • Verified scenarios: invalid string host, non-string host, blank/undefined host, valid trimmed host, existing exec target runtime tests.
  • Edge cases checked: blank host remains equivalent to no override; gateway with whitespace still normalizes correctly; non-string input fails explicitly.
  • What I did not verify: full pnpm build && pnpm check && pnpm test; this PR was validated with focused tests plus check:changed for the touched surface.

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.

Compatibility / Migration

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

Risks and Mitigations

  • Risk: callers relying on malformed host values silently falling back will now get an error.
    • Mitigation: documented the allowed host set and preserved fallback behavior for missing/blank host values.

AI-assisted disclosure

This PR was AI-assisted. I reviewed the diff, understand the changed exec validation path, and ran the validation listed above, including local codex review --base origin/main.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • docs/tools/exec.md (modified, +1/-0)
  • src/agents/bash-tools.exec-foreground-failures.test.ts (modified, +28/-0)
  • src/agents/bash-tools.exec.ts (modified, +3/-2)
  • src/agents/bash-tools.schemas.ts (modified, +6/-5)
  • src/infra/exec-approvals-policy.test.ts (modified, +13/-0)
  • src/infra/exec-approvals.ts (modified, +26/-0)

Code Example

{
  "name": "exec",
  "arguments": { "command": "pgrep -af harvester_scheduler", "host": "spark-ff13" }
}

---

{
  "role": "toolResult",
  "content": [{ "type": "text", "text": "2712 sh -c pgrep -af harvester_scheduler" }],
  "details": { "status": "completed", "exitCode": 0, "cwd": "/home/node/.openclaw/workspace" }
}

---

host: z.enum(["auto", "sandbox", "gateway", "node"]).optional()

---

host: Type.Optional(Type.String({ description: "Exec host/target (auto|sandbox|gateway|node)." }))

---

host: z.enum(["auto", "sandbox", "gateway", "node"]).optional()
  .superRefine((v, ctx) => {
    if (v !== undefined && !["auto","sandbox","gateway","node"].includes(v)) {
      ctx.addIssue({ code: "custom", message: `invalid host: "${v}"; allowed: auto|sandbox|gateway|node` });
    }
  })

---

{
  "details": { "status": "completed", "exitCode": 0, "effectiveHost": "sandbox", "requestedHost": "spark-ff13", ... }
}
RAW_BUFFERClick to expand / collapse

Bug: invalid host: values silently coerce to default target, causing agents to confabulate host-level facts

Summary

The exec tool's host argument is declared as a strict enum (auto | sandbox | gateway | node) in both the Zod schema and the TypeBox descriptor. When an agent passes an unrecognized string (e.g., host: "spark-ff13"), Zod's .optional() strips the invalid value silently, the runtime falls back to the default target, the command runs there, and the tool result returned to the model does not indicate that the requested target was overridden. The model believes the command ran where it asked, and reports pod-local facts as if they were host-level facts.

This produces a deterministic confabulation pattern in agents that wrap host-introspection tasks: the agent sees "no harvester process found" / "endpoint connection refused" from inside the sandbox, concludes the host's harvester is down, and starts trying to restart a service that is actually running fine. The model can't self-correct from the result alone — the response shape is identical to a successful host-targeted call.

Reproduction

OpenClaw ghcr.io/openclaw/openclaw:2026.4.9 running in a k3s pod. Cron prompt instructs the agent to run pgrep -af harvester_scheduler against spark-ff13 (a hostname).

Agent emits:

{
  "name": "exec",
  "arguments": { "command": "pgrep -af harvester_scheduler", "host": "spark-ff13" }
}

Tool returns (truncated):

{
  "role": "toolResult",
  "content": [{ "type": "text", "text": "2712 sh -c pgrep -af harvester_scheduler" }],
  "details": { "status": "completed", "exitCode": 0, "cwd": "/home/node/.openclaw/workspace" }
}

The output 2712 sh -c pgrep -af harvester_scheduler is pgrep matching its own shell invocation — i.e., it ran in the pod (PID namespace 2712 = the shell, NOT the host's harvester PID 1578922). The model interprets this as "no harvester running on spark-ff13" and proceeds to attempt restart commands. Across 24+ subsequent exec calls, the model never recovers the premise.

Across multiple cron runs sampled (n=3): every host: "spark-ff13" and host: "spark-4f0c" call ran in the sandbox. The misdirection is fully deterministic.

Schema lines

  • dist/zod-schema.agent-runtime-*.js line 291:
    host: z.enum(["auto", "sandbox", "gateway", "node"]).optional()
  • dist/exec-defaults-*.js line 578:
    host: Type.Optional(Type.String({ description: "Exec host/target (auto|sandbox|gateway|node)." }))

The TypeBox declaration uses Type.String (free-form) but the zod validation uses z.enum (strict). Note: the description string ("Exec host/target (auto|sandbox|gateway|node).") is what models see in tool catalogs — but zero-shot models commonly translate a hostname token in their prompt into the host arg without re-reading the description.

resolveExecTarget in the same file falls through to configuredTarget ?? "auto" when requestedTarget is null (which is what Zod produces from an invalid optional). No error, no warning surfaced.

Why "just train the model better" doesn't fix this

The model has no observable that says "your host value was overridden." The tool result contains:

  • The pod-local stdout/stderr (correct for the sandbox)
  • details.cwd: /home/node/.openclaw/workspace (an obscure clue that the command ran in the pod, not on the host)

There is no effectiveHost or selectedTarget field in the result. Even an agent that re-reads the schema description would have no signal to correct the wrong premise after the call returned.

Proposed fix (two options, either works)

Option 1 — Strict validation: Switch from z.enum().optional() to a refined schema that throws on unrecognized strings:

host: z.enum(["auto", "sandbox", "gateway", "node"]).optional()
  .superRefine((v, ctx) => {
    if (v !== undefined && !["auto","sandbox","gateway","node"].includes(v)) {
      ctx.addIssue({ code: "custom", message: `invalid host: "${v}"; allowed: auto|sandbox|gateway|node` });
    }
  })

This makes the model see a clear error like "host": "spark-ff13" is not a valid value, allowed: auto|sandbox|gateway|node and self-correct on the next turn.

Option 2 — Surface the resolved target in every result: Include effectiveHost in details:

{
  "details": { "status": "completed", "exitCode": 0, "effectiveHost": "sandbox", "requestedHost": "spark-ff13", ... }
}

This lets the model and downstream tooling detect the override. Less aggressive than (1) — backwards-compatible with existing prompts that pass invalid values.

Both are useful. (2) is the more defensible default; (1) plus (2) together is the strongest.

Impact

This bug is a real source of confabulation in cron-driven autonomous agents that take action on host services. The agent reaches a confident wrong conclusion and acts on it (attempts restarts, sends alerts, etc.). The blast radius scales with the agent's autonomy.

We've worked around it on our side via prompt rewrites (use ssh scr00ge@<ip> and curl http://<ip>:<port> from inside the sandbox, never touch the host: arg) and an in-workspace skill that teaches "verify via a second route before concluding 'X is broken'." But the underlying tool-layer behavior is the right place to fix it for everyone.

Happy to PR (1) or (2) if there's interest. Just confirming intent and which direction OpenClaw maintainers prefer before opening one.

extent analysis

TL;DR

The most likely fix is to implement strict validation for the host argument using a refined Zod schema or surface the resolved target in every result by including effectiveHost in the details object.

Guidance

  • Implement strict validation for the host argument using a refined Zod schema that throws an error on unrecognized strings, as shown in the proposed fix Option 1.
  • Alternatively, surface the resolved target in every result by including effectiveHost in the details object, as shown in proposed fix Option 2.
  • Consider combining both options for the strongest solution.
  • Verify the fix by testing with invalid host values and checking for the expected error message or effectiveHost field in the response.

Example

host: z.enum(["auto", "sandbox", "gateway", "node"]).optional()
  .superRefine((v, ctx) => {
    if (v !== undefined && !["auto","sandbox","gateway","node"].includes(v)) {
      ctx.addIssue({ code: "custom", message: `invalid host: "${v}"; allowed: auto|sandbox|gateway|node` });
    }
  })

Notes

The proposed fixes assume that the Zod schema and TypeBox descriptor are the root cause of the issue. If the issue persists after implementing these fixes, further investigation may be necessary.

Recommendation

Apply workaround by implementing Option 2, surfacing the resolved target in every result, as it is a more defensible default and backwards-compatible with existing prompts that pass invalid values.

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 Exec tool: invalid `host:` values silently coerce to sandbox, causing agents to confabulate host-level facts [1 pull requests, 1 comments, 2 participants]