Skip to content

fix(cli): mark embedded agent fallback#72730

Merged
amknight merged 3 commits into
mainfrom
codex/cli-agent-fallback-marker
Apr 27, 2026
Merged

fix(cli): mark embedded agent fallback#72730
amknight merged 3 commits into
mainfrom
codex/cli-agent-fallback-marker

Conversation

@amknight

Copy link
Copy Markdown
Member

Summary

Root Cause

agentCliCommand caught Gateway request failures and immediately ran the embedded agent. For --json, the resulting embedded payload looked like a normal run result, so scripts could not reliably distinguish a Gateway run from an embedded fallback run.

Validation

  • pnpm test src/commands/agent-via-gateway.test.ts
  • git diff --check HEAD~1..HEAD
  • pnpm check:changed reached core/core-test typecheck and then failed in lint:core on pre-existing src/agents/lanes.ts typescript-eslint(no-unsafe-enum-comparison); this branch does not modify that file (git diff origin/main -- src/agents/lanes.ts is empty).

Fixes #71416.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation commands Command implementations size: S maintainer Maintainer-authored PR labels Apr 27, 2026
@amknight
amknight force-pushed the codex/cli-agent-fallback-marker branch from 8b6e8f3 to 07c2152 Compare April 27, 2026 10:06
@openclaw-barnacle openclaw-barnacle Bot added the agents Agent runtime and tooling label Apr 27, 2026
@amknight
amknight marked this pull request as ready for review April 27, 2026 11:43
@greptile-apps

greptile-apps Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an explicit EMBEDDED FALLBACK stderr diagnostic and machine-readable meta.transport: "embedded" / meta.fallbackFrom: "gateway" markers to the JSON output when agentCliCommand falls back from Gateway to the embedded agent. The core implementation is clean: a mergeResultMetaOverrides helper in delivery.ts, the constant EMBEDDED_FALLBACK_META passed on the fallback path, and the switch from runtime.log(JSON.stringify(...)) to writeRuntimeJson for proper output-channel routing.

Two issues in CHANGELOG.md need attention before merge:

Confidence Score: 3/5

Not safe to merge as-is due to two CHANGELOG integrity problems: a dropped contributor credit and two unrelated fix entries with no backing code changes.

Two P1 findings in CHANGELOG.md — one silently removes a contributor's attribution, the other adds changelog entries for fixes whose code is absent from the PR — bring the ceiling to 4, and having two distinct P1s in the same file warrants pulling the score down to 3.

CHANGELOG.md requires review of the modified Fixes #72459 thanks line and the two newly added entries for Agents/tools and Agents/subagents.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: CHANGELOG.md
Line: 83

Comment:
**Contributor credit accidentally removed**

The existing entry for Fixes #72459 had `Thanks @1fanwang and @amknight`; this PR changes it to `Thanks @amkwright`, silently dropping `@1fanwang`'s attribution. This appears to be an unintended edit — it's not mentioned in the PR description and nothing else in the diff touches that issue.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: CHANGELOG.md
Line: 24-25

Comment:
**CHANGELOG entries with no corresponding code changes in this PR**

Two new entries reference issues and code areas unrelated to this PR's diff:
- `Agents/tools: normalize null or missing tool-call arguments… Fixes #72587`
- `Agents/subagents: clear active embedded-run state… (#70187)`

No file in this PR's changeset touches tool-argument normalization or subagent lifecycle bookkeeping. If these fixes belong to a different PR/branch, adding their CHANGELOG entries here creates an inaccurate history and could cause the entries to appear under the wrong release cut.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/commands/agent-via-gateway.test.ts
Line: 217-226

Comment:
**JSON output assertion targets the wrong runtime channel**

The mock manually calls `rt?.log?(JSON.stringify(...))`, so the assertion on `jsonRuntime.log` succeeds. But `jsonRuntime` is an `OutputRuntimeEnv` (it has `writeJson`), so the real `deliverAgentCommandResult` path routes JSON through `writeRuntimeJson → runtime.writeJson`, never touching `runtime.log`. The test therefore validates mock behavior rather than the production output channel.

`delivery.test.ts` covers the real path correctly (`expect(runtime.writeJson).toHaveBeenCalledWith(...)`). If this test is meant to be an integration-level check, the mock could emit output via `rt?.writeJson?.(...)` instead so the assertion mirrors what production actually does.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "refactor(cli): structure embedded fallba..." | Re-trigger Greptile

Comment thread CHANGELOG.md
Comment on lines 24 to +25
- Control UI/Dreaming: require explicit confirmation before applying restart-impacting Dreaming mode changes, with restart warning copy and loading feedback. Fixes #63804. (#63807) Thanks @bbddbb1.
- CLI/agent: mark Gateway-to-embedded fallback runs with `meta.transport: "embedded"` and `meta.fallbackFrom: "gateway"` in JSON output, and make the terminal diagnostic explicit so scripts and operators can distinguish fallback runs from Gateway runs. Fixes #71416. Thanks @amknight.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 CHANGELOG entries with no corresponding code changes in this PR

Two new entries reference issues and code areas unrelated to this PR's diff:

  • Agents/tools: normalize null or missing tool-call arguments… Fixes #72587
  • Agents/subagents: clear active embedded-run state… (#70187)

No file in this PR's changeset touches tool-argument normalization or subagent lifecycle bookkeeping. If these fixes belong to a different PR/branch, adding their CHANGELOG entries here creates an inaccurate history and could cause the entries to appear under the wrong release cut.

Prompt To Fix With AI
This is a comment left during a code review.
Path: CHANGELOG.md
Line: 24-25

Comment:
**CHANGELOG entries with no corresponding code changes in this PR**

Two new entries reference issues and code areas unrelated to this PR's diff:
- `Agents/tools: normalize null or missing tool-call arguments… Fixes #72587`
- `Agents/subagents: clear active embedded-run state… (#70187)`

No file in this PR's changeset touches tool-argument normalization or subagent lifecycle bookkeeping. If these fixes belong to a different PR/branch, adding their CHANGELOG entries here creates an inaccurate history and could cause the entries to appear under the wrong release cut.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional, missed in previous PRs

Comment on lines 217 to +226
expect(loggingState.forceConsoleToStderr).toBe(true);
expect(jsonRuntime.log).toHaveBeenCalledTimes(1);
const payload = JSON.parse(String(jsonRuntime.log.mock.calls[0]?.[0]));
expect(payload).toMatchObject({
payloads: [{ text: "local" }],
meta: {
durationMs: 1,
transport: "embedded",
fallbackFrom: "gateway",
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 JSON output assertion targets the wrong runtime channel

The mock manually calls rt?.log?(JSON.stringify(...)), so the assertion on jsonRuntime.log succeeds. But jsonRuntime is an OutputRuntimeEnv (it has writeJson), so the real deliverAgentCommandResult path routes JSON through writeRuntimeJson → runtime.writeJson, never touching runtime.log. The test therefore validates mock behavior rather than the production output channel.

delivery.test.ts covers the real path correctly (expect(runtime.writeJson).toHaveBeenCalledWith(...)). If this test is meant to be an integration-level check, the mock could emit output via rt?.writeJson?.(...) instead so the assertion mirrors what production actually does.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/agent-via-gateway.test.ts
Line: 217-226

Comment:
**JSON output assertion targets the wrong runtime channel**

The mock manually calls `rt?.log?(JSON.stringify(...))`, so the assertion on `jsonRuntime.log` succeeds. But `jsonRuntime` is an `OutputRuntimeEnv` (it has `writeJson`), so the real `deliverAgentCommandResult` path routes JSON through `writeRuntimeJson → runtime.writeJson`, never touching `runtime.log`. The test therefore validates mock behavior rather than the production output channel.

`delivery.test.ts` covers the real path correctly (`expect(runtime.writeJson).toHaveBeenCalledWith(...)`). If this test is meant to be an integration-level check, the mock could emit output via `rt?.writeJson?.(...)` instead so the assertion mirrors what production actually does.

How can I resolve this? If you propose a fix, please make it concise.

@openclaw openclaw deleted a comment from greptile-apps Bot Apr 27, 2026
@aisle-research-bot

aisle-research-bot Bot commented Apr 27, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Authorization/policy bypass via unconditional gateway→embedded fallback in agentCliCommand
2 🟡 Medium Sensitive information disclosure via stringified gateway error in embedded fallback log
1. 🟠 Authorization/policy bypass via unconditional gateway→embedded fallback in agentCliCommand
Property Value
Severity High
CWE CWE-285
Location src/commands/agent-via-gateway.ts:205-218

Description

The CLI command agentCliCommand falls back to running the embedded agent whenever agentViaGatewayCommand throws any error.

This is security-sensitive because agentViaGatewayCommand calls the Gateway RPC (callGateway({ method: "agent", ... })), which can fail for reasons beyond connectivity—e.g. authorization/permission/policy enforcement.

From src/gateway/client.ts, gateway-side errors are surfaced as GatewayClientRequestError with a gatewayCode (e.g., permission/policy/unauthenticated-type codes). Those errors would be caught and silently downgraded to embedded execution, potentially bypassing:

  • Gateway-side approvals / enterprise policy checks
  • Centralized auditing or sandboxing controls enforced by the gateway
  • “Remote-only” restrictions that are meant to prevent local execution

Vulnerable code:

try {
  return await agentViaGatewayCommand(opts, runtime);
} catch (err) {
  runtime.error?.(
    `EMBEDDED FALLBACK: Gateway agent failed; running embedded agent: ${String(err)}`,
  );
  return await agentCommand(
    {
      ...localOpts,
      resultMetaOverrides: EMBEDDED_FALLBACK_META,
    },
    runtime,
    deps,
  );
}

Recommendation

Only fall back to embedded execution for a narrow set of connectivity/unavailability failures, and never for authorization/policy/validation failures.

Suggested approach:

  • Detect gateway request errors (e.g., err instanceof GatewayClientRequestError) and check err.gatewayCode.
  • Only allow fallback for codes like UNAVAILABLE, TIMEOUT, connection close/handshake failures.
  • Re-throw (or surface) errors like UNAUTHENTICATED, PERMISSION_DENIED, POLICY_DENIED, etc.
  • Consider requiring an explicit --local flag (or config setting) to permit embedded execution at all.

Example:

import { GatewayClientRequestError } from "../gateway/client.js";

try {
  return await agentViaGatewayCommand(opts, runtime);
} catch (err) {
  if (err instanceof GatewayClientRequestError) {
    const fallbackAllowed = ["UNAVAILABLE", "DEADLINE_EXCEEDED"].includes(err.gatewayCode);
    if (!fallbackAllowed) throw err; // don’t bypass gateway enforcement
  }// Optionally: also only fallback for network-ish Errors (ECONNREFUSED, etc.)
  return await agentCommand({ ...localOpts, resultMetaOverrides: EMBEDDED_FALLBACK_META }, runtime, deps);
}
2. 🟡 Sensitive information disclosure via stringified gateway error in embedded fallback log
Property Value
Severity Medium
CWE CWE-532
Location src/commands/agent-via-gateway.ts:208-210

Description

In agentCliCommand, the gateway failure is logged to stderr using String(err). Errors thrown from callGateway() commonly embed connectionDetails.message, which includes the full gateway target URL (from --url / OPENCLAW_GATEWAY_URL) and config path. If the URL contains embedded credentials (e.g., wss://user:pass@​host) or sensitive query parameters/tokens, these can be leaked to terminal/CI logs.

  • Input/source: gateway URL from CLI --url or env OPENCLAW_GATEWAY_URL (and potentially other error message content)
  • Sink: runtime.error (stderr) with unredacted String(err)

Vulnerable code:

runtime.error?.(
  `EMBEDDED FALLBACK: Gateway agent failed; running embedded agent: ${String(err)}`,
);

Because this path triggers specifically when the gateway call fails, it may occur in automated environments where stderr is collected centrally, increasing the impact of accidental secret exposure.

Recommendation

Avoid logging raw error stringification; log a sanitized, minimal message.

  • Prefer err instanceof Error ? err.message : "<unknown error>" (not stack).
  • Redact credentials/query parameters from URLs before printing.

Example:

function redactUrlSecrets(text: string): string {// Remove userinfo and common token query params
  return text
    .replace(/(wss?:\/\/)([^\s/@]+):([^\s/@]+)@/g, "$1***:***@")
    .replace(/([?&](token|access_token|auth|password)=)[^&#\s]+/gi, "$1***");
}

const msg = err instanceof Error ? err.message : String(err);
runtime.error?.(
  `EMBEDDED FALLBACK: Gateway agent failed; running embedded agent: ${redactUrlSecrets(msg)}`,
);

If structured JSON output is expected, consider emitting only a short fixed diagnostic to stderr (no interpolation), and include sanitized failure metadata in the JSON meta instead.


Analyzed PR: #72730 at commit 536f1e3

Last updated on: 2026-04-27T12:12:00Z

@amknight
amknight merged commit b1e530b into main Apr 27, 2026
64 of 69 checks passed
@amknight
amknight deleted the codex/cli-agent-fallback-marker branch April 27, 2026 12:14
@clawsweeper clawsweeper Bot mentioned this pull request Apr 30, 2026
25 tasks
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
* fix(cli): mark embedded agent fallback

* refactor(cli): structure embedded fallback metadata

* refactor(cli): move fallback metadata types out of EmbeddedPiRunMeta

---------

Co-authored-by: Alex Knight <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* fix(cli): mark embedded agent fallback

* refactor(cli): structure embedded fallback metadata

* refactor(cli): move fallback metadata types out of EmbeddedPiRunMeta

---------

Co-authored-by: Alex Knight <[email protected]>
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
* fix(cli): mark embedded agent fallback

* refactor(cli): structure embedded fallback metadata

* refactor(cli): move fallback metadata types out of EmbeddedPiRunMeta

---------

Co-authored-by: Alex Knight <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
* fix(cli): mark embedded agent fallback

* refactor(cli): structure embedded fallback metadata

* refactor(cli): move fallback metadata types out of EmbeddedPiRunMeta

---------

Co-authored-by: Alex Knight <[email protected]>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
* fix(cli): mark embedded agent fallback

* refactor(cli): structure embedded fallback metadata

* refactor(cli): move fallback metadata types out of EmbeddedPiRunMeta

---------

Co-authored-by: Alex Knight <[email protected]>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
* fix(cli): mark embedded agent fallback

* refactor(cli): structure embedded fallback metadata

* refactor(cli): move fallback metadata types out of EmbeddedPiRunMeta

---------

Co-authored-by: Alex Knight <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations docs Improvements or additions to documentation maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI silently falls back to embedded mode when gateway is unreachable

1 participant