From 8b2eca23ecb5beb4681798d213041b7909a7436b Mon Sep 17 00:00:00 2001 From: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 03:58:04 +0000 Subject: [PATCH] fix(clawsweeper): address review for automerge-openclaw-openclaw-75004 (1) --- src/infra/command-explainer/extract.test.ts | 25 ++++++++++++++++++- .../command-explainer/tree-sitter-runtime.ts | 11 +++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/infra/command-explainer/extract.test.ts b/src/infra/command-explainer/extract.test.ts index 9161d2ed0a3..cc4157a0705 100644 --- a/src/infra/command-explainer/extract.test.ts +++ b/src/infra/command-explainer/extract.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import type { Parser } from "web-tree-sitter"; import { explainShellCommand } from "./extract.js"; import { @@ -20,6 +20,7 @@ afterEach(() => { setBashParserLoaderForCommandExplanationForTest(); parserLoaderOverridden = false; } + vi.restoreAllMocks(); }); describe("command explainer tree-sitter runtime", () => { @@ -71,6 +72,28 @@ describe("command explainer tree-sitter runtime", () => { ).toThrow("Unable to locate missing-openclaw-parser.wasm in web-tree-sitter"); }); + it("reports parser progress cancellation as a timeout", async () => { + const reset = vi.fn(); + const parser = { + parse: ( + _source: string, + _oldTree: unknown, + options?: { progressCallback?: (state: unknown) => boolean }, + ) => { + options?.progressCallback?.({ currentOffset: 0, hasError: false }); + return null; + }, + reset, + } as unknown as Parser; + vi.spyOn(performance, "now").mockReturnValueOnce(0).mockReturnValue(501); + setParserLoaderForTest(async () => parser); + + await expect(parseBashForCommandExplanation("echo hi")).rejects.toThrow( + "tree-sitter-bash timed out after 500ms while parsing shell command", + ); + expect(reset).toHaveBeenCalledOnce(); + }); + it("explains a pipeline with python inline eval", async () => { const explanation = await explainShellCommand('ls | grep "stuff" | python -c \'print("hi")\''); diff --git a/src/infra/command-explainer/tree-sitter-runtime.ts b/src/infra/command-explainer/tree-sitter-runtime.ts index 140ab9ec158..08792a7851a 100644 --- a/src/infra/command-explainer/tree-sitter-runtime.ts +++ b/src/infra/command-explainer/tree-sitter-runtime.ts @@ -87,11 +87,20 @@ export async function parseBashForCommandExplanation(source: string): Promise performance.now() > deadlineMs, + progressCallback: () => { + timedOut = performance.now() > deadlineMs; + return timedOut; + }, }); if (!tree) { parser.reset(); + if (timedOut) { + throw new Error( + `tree-sitter-bash timed out after ${MAX_COMMAND_EXPLANATION_PARSE_MS}ms while parsing shell command`, + ); + } throw new Error("tree-sitter-bash returned no parse tree"); } return tree;