fix(cli): mark embedded agent fallback#72730
Conversation
8b6e8f3 to
07c2152
Compare
Greptile SummaryThis PR adds an explicit Two issues in
Confidence Score: 3/5Not 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 AIThis 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 |
| - 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. |
There was a problem hiding this 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 #72587Agents/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.There was a problem hiding this comment.
Intentional, missed in previous PRs
| 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", | ||
| }, |
There was a problem hiding this 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.
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.
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟠 Authorization/policy bypass via unconditional gateway→embedded fallback in agentCliCommand
DescriptionThe CLI command This is security-sensitive because From
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,
);
}RecommendationOnly fall back to embedded execution for a narrow set of connectivity/unavailability failures, and never for authorization/policy/validation failures. Suggested approach:
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
DescriptionIn
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. RecommendationAvoid logging raw error stringification; log a sanitized, minimal message.
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 Analyzed PR: #72730 at commit Last updated on: 2026-04-27T12:12:00Z |
* 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]>
* 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]>
* 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]>
* 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]>
* 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]>
* 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]>
Summary
openclaw agentGateway-to-embedded fallback visible with an explicitEMBEDDED FALLBACKstderr diagnostic.meta.transport: "embedded"andmeta.fallbackFrom: "gateway".Root Cause
agentCliCommandcaught 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.tsgit diff --check HEAD~1..HEADpnpm check:changedreached core/core-test typecheck and then failed inlint:coreon pre-existingsrc/agents/lanes.tstypescript-eslint(no-unsafe-enum-comparison); this branch does not modify that file (git diff origin/main -- src/agents/lanes.tsis empty).Fixes #71416.