fix: hide empty execute tool calls (#25346)

This commit is contained in:
Danielle Maywood
2026-05-14 18:19:12 +01:00
committed by GitHub
parent 2871a02352
commit 68baf84b8c
3 changed files with 88 additions and 26 deletions
@@ -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,
@@ -77,6 +77,7 @@ const ExecuteToolInner: React.FC<ExecuteToolInnerProps> = ({
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<ExecuteToolInnerProps> = ({
: "Expand command output";
const durationLabel = formatShellDurationMs(durationMs);
if (!hasCommand) {
return null;
}
return (
<div className="group/exec grid w-full grid-cols-[minmax(0,1fr)_auto] items-start gap-x-2 rounded-md bg-surface-primary font-mono text-xs leading-5">
<div className="group/exec grid w-full grid-cols-[minmax(0,1fr)_auto] items-start gap-x-2 rounded-md bg-surface-primary font-mono font-normal text-xs leading-5">
<Tooltip delayDuration={300}>
<TooltipTrigger asChild>
{hasOutput ? (
@@ -96,7 +101,7 @@ const ExecuteToolInner: React.FC<ExecuteToolInnerProps> = ({
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"
>
<ShellCommandLine
command={command}
@@ -105,7 +110,7 @@ const ExecuteToolInner: React.FC<ExecuteToolInnerProps> = ({
/>
</button>
) : (
<div className="col-start-1 row-start-1 flex min-w-0 items-center gap-2 text-content-secondary">
<div className="col-start-1 row-start-1 flex min-w-0 items-center gap-2 font-normal text-content-secondary">
<ShellCommandLine
command={command}
durationLabel={durationLabel}
@@ -113,7 +118,7 @@ const ExecuteToolInner: React.FC<ExecuteToolInnerProps> = ({
</div>
)}
</TooltipTrigger>
<TooltipContent className="max-w-xl whitespace-pre-wrap break-all font-mono">
<TooltipContent className="max-w-xl whitespace-pre-wrap break-all font-mono font-normal">
{command}
</TooltipContent>
</Tooltip>
@@ -182,12 +187,14 @@ const ShellCommandLine: React.FC<{
}> = ({ command, durationLabel, expanded }) => {
return (
<>
<span className="shrink-0 text-[13px] text-content-success">$</span>
<span className="block min-w-0 truncate text-[13px] text-content-primary">
<span className="shrink-0 text-[13px] font-normal text-content-success">
$
</span>
<span className="block min-w-0 truncate text-[13px] font-normal text-content-primary">
{command}
</span>
{durationLabel && (
<span className="shrink-0 text-[13px] text-content-secondary">
<span className="shrink-0 text-[13px] font-normal text-content-secondary">
{durationLabel}
</span>
)}
@@ -214,14 +214,19 @@ const parseAskUserQuestionResult = (
return null;
};
const ExecuteRenderer: FC<ToolRendererProps> = ({
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<ToolRendererProps> = ({
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<ToolRendererProps> = ({
status,
args,
result,
isError,
killedBySignal,
shellToolDisplayMode,
}) => {
const data = getExecuteRenderData(args, result);
if (shouldHideExecuteTool(data)) {
return null;
}
if (data.authenticateURL) {
return (
<ExecuteAuthRequiredTool
command={command}
output={output}
authenticateURL={authenticateURL}
providerLabel={providerLabel}
command={data.command}
output={data.output}
authenticateURL={data.authenticateURL}
providerLabel={data.providerLabel}
/>
);
}
return (
<ExecuteToolComponent
command={command}
output={output}
command={data.command}
output={data.output}
status={status}
isError={isError}
durationMs={durationMs}
isBackgrounded={isBackgrounded}
durationMs={data.durationMs}
isBackgrounded={data.isBackgrounded}
killedBySignal={killedBySignal}
shellToolDisplayMode={shellToolDisplayMode}
/>
@@ -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 (
<div