fix(auto-reply): bound scp staging stderr · openclaw/openclaw@b474130
vincentkoc
·
2026-05-28
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -29,7 +29,11 @@ vi.mock("node:child_process", async () => {
|
29 | 29 | }; |
30 | 30 | }); |
31 | 31 | |
32 | | -import { stageSandboxMedia } from "./reply/stage-sandbox-media.js"; |
| 32 | +import { |
| 33 | +appendScpStderrTail, |
| 34 | +SCP_STDERR_TAIL_CHARS, |
| 35 | +stageSandboxMedia, |
| 36 | +} from "./reply/stage-sandbox-media.js"; |
33 | 37 | |
34 | 38 | afterEach(() => { |
35 | 39 | vi.restoreAllMocks(); |
@@ -84,6 +88,14 @@ function requireFirstMockCall(mock: { mock: { calls: unknown[][] } }, label: str
|
84 | 88 | } |
85 | 89 | |
86 | 90 | describe("stageSandboxMedia scp remote paths", () => { |
| 91 | +it("keeps only the tail of noisy scp stderr", () => { |
| 92 | +const stderr = appendScpStderrTail("start-", `${"x".repeat(SCP_STDERR_TAIL_CHARS)}-end`); |
| 93 | + |
| 94 | +expect(stderr).toHaveLength(SCP_STDERR_TAIL_CHARS); |
| 95 | +expect(stderr).toContain("-end"); |
| 96 | +expect(stderr).not.toContain("start-"); |
| 97 | +}); |
| 98 | + |
87 | 99 | it("rejects remote attachment filenames with shell metacharacters before spawning scp", async () => { |
88 | 100 | await withSandboxMediaTempHome("openclaw-triggers-", async (home) => { |
89 | 101 | const { cfg, workspaceDir, sessionKey, remoteCacheDir } = createRemoteStageParams(home); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -19,6 +19,7 @@ import { CONFIG_DIR } from "../../utils.js";
|
19 | 19 | import type { MsgContext, TemplateContext } from "../templating.js"; |
20 | 20 | |
21 | 21 | const STAGED_MEDIA_MAX_BYTES = MEDIA_MAX_BYTES; |
| 22 | +export const SCP_STDERR_TAIL_CHARS = 16_384; |
22 | 23 | |
23 | 24 | // `staged` maps every absolute source path that was copied into the sandbox |
24 | 25 | // (or remote cache) to its rewritten ctx path. Callers like chat.send's |
@@ -335,7 +336,7 @@ async function scpFile(remoteHost: string, remotePath: string, localPath: string
|
335 | 336 | let stderr = ""; |
336 | 337 | child.stderr?.setEncoding("utf8"); |
337 | 338 | child.stderr?.on("data", (chunk) => { |
338 | | -stderr += chunk; |
| 339 | +stderr = appendScpStderrTail(stderr, chunk); |
339 | 340 | }); |
340 | 341 | |
341 | 342 | child.once("error", reject); |
@@ -348,3 +349,15 @@ async function scpFile(remoteHost: string, remotePath: string, localPath: string
|
348 | 349 | }); |
349 | 350 | }); |
350 | 351 | } |
| 352 | + |
| 353 | +export function appendScpStderrTail( |
| 354 | +current: string, |
| 355 | +chunk: string, |
| 356 | +maxChars = SCP_STDERR_TAIL_CHARS, |
| 357 | +): string { |
| 358 | +const combined = `${current}${chunk}`; |
| 359 | +if (combined.length <= maxChars) { |
| 360 | +return combined; |
| 361 | +} |
| 362 | +return combined.slice(-maxChars); |
| 363 | +} |
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。