From 68baf84b8c539b729c79ffd5edd8207e8908843e Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 14 May 2026 18:19:12 +0100 Subject: [PATCH] fix: hide empty execute tool calls (#25346) --- .../tools/ExecuteTool.stories.tsx | 15 ++++ .../ChatElements/tools/ExecuteTool.tsx | 21 +++-- .../components/ChatElements/tools/Tool.tsx | 78 ++++++++++++++----- 3 files changed, 88 insertions(+), 26 deletions(-) diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.stories.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.stories.tsx index fd8b1f252d..2400d856ab 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.stories.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.stories.tsx @@ -32,6 +32,21 @@ export const ShortCommand: Story = { }, }; +export const RunningWithoutCommand: Story = { + args: { + command: "", + status: "running", + output: "", + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + expect(canvas.queryByText("$")).not.toBeInTheDocument(); + expect( + canvas.queryByRole("button", { name: "Copy command" }), + ).not.toBeInTheDocument(); + }, +}; + export const LongCommand: Story = { args: { command: longCommand, diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.tsx index 60e6171649..1605c45e69 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ExecuteTool.tsx @@ -77,6 +77,7 @@ const ExecuteToolInner: React.FC = ({ killedBySignal, outputInitiallyOpen, }) => { + const hasCommand = command.trim().length > 0; const hasOutput = output.length > 0; const isRunning = status === "running"; const showFailureIndicator = isError && !isRunning; @@ -86,8 +87,12 @@ const ExecuteToolInner: React.FC = ({ : "Expand command output"; const durationLabel = formatShellDurationMs(durationMs); + if (!hasCommand) { + return null; + } + return ( -
+
{hasOutput ? ( @@ -96,7 +101,7 @@ const ExecuteToolInner: React.FC = ({ aria-expanded={outputOpen} aria-label={outputToggleLabel} onClick={() => setOutputOpen((value) => !value)} - className="col-start-1 row-start-1 m-0 flex w-full min-w-0 cursor-pointer items-center gap-2 border-0 bg-transparent p-0 text-left font-[inherit] text-[inherit] text-content-secondary transition-colors hover:text-content-primary" + className="col-start-1 row-start-1 m-0 flex w-full min-w-0 cursor-pointer items-center gap-2 border-0 bg-transparent p-0 text-left font-[inherit] font-normal text-[inherit] text-content-secondary transition-colors hover:text-content-primary" > = ({ /> ) : ( -
+
= ({
)} - + {command} @@ -182,12 +187,14 @@ const ShellCommandLine: React.FC<{ }> = ({ command, durationLabel, expanded }) => { return ( <> - $ - + + $ + + {command} {durationLabel && ( - + {durationLabel} )} diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx index b3c9179777..dffc5df360 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx @@ -214,14 +214,19 @@ const parseAskUserQuestionResult = ( return null; }; -const ExecuteRenderer: FC = ({ - status, - args, - result, - isError, - killedBySignal, - shellToolDisplayMode, -}) => { +type ExecuteRenderData = { + command: string; + output: string; + durationMs?: number; + isBackgrounded: boolean; + authenticateURL: string; + providerLabel: string; +}; + +const getExecuteRenderData = ( + args: unknown, + result: unknown, +): ExecuteRenderData => { const parsedArgs = parseArgs(args); const command = parsedArgs ? asString(parsedArgs.command) : ""; const rec = asRecord(result); @@ -233,32 +238,61 @@ const ExecuteRenderer: FC = ({ const isBackgrounded = Boolean( rec && asString(rec.background_process_id).trim(), ); - const authRequired = rec ? Boolean(rec.auth_required) : false; - const authenticateURL = rec ? asString(rec.authenticate_url).trim() : ""; + const authenticateURL = rec?.auth_required + ? asString(rec.authenticate_url).trim() + : ""; const providerLabel = toProviderLabel( rec ? asString(rec.provider_display_name).trim() : "", rec ? asString(rec.provider_id).trim() : "", rec ? asString(rec.provider_type).trim() : "", ); - if (authRequired && authenticateURL) { + return { + command, + output, + durationMs, + isBackgrounded, + authenticateURL, + providerLabel, + }; +}; + +const shouldHideExecuteTool = (data: ExecuteRenderData): boolean => { + return data.command.trim().length === 0 && !data.authenticateURL; +}; + +const ExecuteRenderer: FC = ({ + status, + args, + result, + isError, + killedBySignal, + shellToolDisplayMode, +}) => { + const data = getExecuteRenderData(args, result); + + if (shouldHideExecuteTool(data)) { + return null; + } + + if (data.authenticateURL) { return ( ); } return ( @@ -1059,6 +1093,12 @@ export const Tool = memo( ? SubagentRenderer : (toolRenderers[name] ?? GenericToolRenderer); const isShellTool = name === "execute" || name === "process_output"; + if ( + name === "execute" && + shouldHideExecuteTool(getExecuteRenderData(args, result)) + ) { + return null; + } return (