2025-04-24 18:03:33 -07:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import fs from 'fs';
|
2025-04-27 18:57:10 -07:00
|
|
|
import path from 'path';
|
2025-05-06 10:44:40 -07:00
|
|
|
import os from 'os';
|
|
|
|
|
import crypto from 'crypto';
|
2025-04-24 18:03:33 -07:00
|
|
|
import { Config } from '../config/config.js';
|
2025-04-25 14:05:58 -07:00
|
|
|
import {
|
|
|
|
|
BaseTool,
|
|
|
|
|
ToolResult,
|
|
|
|
|
ToolCallConfirmationDetails,
|
|
|
|
|
ToolExecuteConfirmationDetails,
|
|
|
|
|
ToolConfirmationOutcome,
|
|
|
|
|
} from './tools.js';
|
2025-04-27 18:57:10 -07:00
|
|
|
import { SchemaValidator } from '../utils/schemaValidator.js';
|
fix(shell): Improve error reporting for shell command failures
This commit enhances the tool to provide more informative feedback to the user when a shell command fails, especially in non-debug mode.
Previously, if a command terminated due to a signal (e.g., SIGPIPE during a with no upstream) or failed without producing stdout/stderr, the user would see no output, making it difficult to diagnose the issue.
Changes:
- Modified to update the logic.
- If a command produces no direct output but results in an error, signal, non-zero exit code, or user cancellation, a concise message indicating this outcome is now shown in .
- Utilized the existing utility from for consistent error message formatting, which also resolved previous TypeScript type inference issues.
This ensures users receive clearer feedback on command execution status, improving the tool's usability and aiding in troubleshooting.
Fixes https://b.corp.google.com/issues/417998119
2025-05-18 00:23:57 -07:00
|
|
|
import { getErrorMessage } from '../utils/errors.js';
|
2025-06-02 14:50:12 -07:00
|
|
|
import stripAnsi from 'strip-ansi';
|
|
|
|
|
|
2025-04-24 18:03:33 -07:00
|
|
|
export interface ShellToolParams {
|
|
|
|
|
command: string;
|
|
|
|
|
description?: string;
|
2025-04-27 18:57:10 -07:00
|
|
|
directory?: string;
|
2025-04-24 18:03:33 -07:00
|
|
|
}
|
2025-04-27 18:57:10 -07:00
|
|
|
import { spawn } from 'child_process';
|
2025-04-24 18:03:33 -07:00
|
|
|
|
2025-05-30 01:58:09 -07:00
|
|
|
const OUTPUT_UPDATE_INTERVAL_MS = 1000;
|
|
|
|
|
|
2025-04-24 18:03:33 -07:00
|
|
|
export class ShellTool extends BaseTool<ShellToolParams, ToolResult> {
|
2025-06-09 08:57:30 -07:00
|
|
|
static Name: string = 'run_shell_command';
|
2025-04-25 14:05:58 -07:00
|
|
|
private whitelist: Set<string> = new Set();
|
2025-04-24 18:03:33 -07:00
|
|
|
|
2025-05-02 09:31:18 -07:00
|
|
|
constructor(private readonly config: Config) {
|
2025-04-24 18:03:33 -07:00
|
|
|
const toolDisplayName = 'Shell';
|
Ignore folders files (#651)
# Add .gitignore-Aware File Filtering to gemini-cli
This pull request introduces .gitignore-based file filtering to the gemini-cli, ensuring that git-ignored files are automatically excluded from file-related operations and suggestions throughout the CLI. The update enhances usability, reduces noise from build artifacts and dependencies, and provides new configuration options for fine-tuning file discovery.
Key Improvements
.gitignore File Filtering
All @ (at) commands, file completions, and core discovery tools now honor .gitignore patterns by default.
Git-ignored files (such as node_modules/, dist/, .env, and .git) are excluded from results unless explicitly overridden.
The behavior can be customized via a new fileFiltering section in settings.json, including options for:
Turning .gitignore respect on/off.
Adding custom ignore patterns.
Allowing or excluding build artifacts.
Configuration & Documentation Updates
settings.json schema extended with fileFiltering options.
Documentation updated to explain new filtering controls and usage patterns.
Testing
New and updated integration/unit tests for file filtering logic, configuration merging, and edge cases.
Test coverage ensures .gitignore filtering works as intended across different workflows.
Internal Refactoring
Core file discovery logic refactored for maintainability and extensibility.
Underlying tools (ls, glob, read-many-files) now support git-aware filtering out of the box.
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
2025-06-03 21:40:46 -07:00
|
|
|
|
|
|
|
|
let toolDescription: string;
|
|
|
|
|
let toolParameterSchema: Record<string, unknown>;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const descriptionUrl = new URL('shell.md', import.meta.url);
|
|
|
|
|
toolDescription = fs.readFileSync(descriptionUrl, 'utf-8');
|
|
|
|
|
const schemaUrl = new URL('shell.json', import.meta.url);
|
|
|
|
|
toolParameterSchema = JSON.parse(fs.readFileSync(schemaUrl, 'utf-8'));
|
|
|
|
|
} catch {
|
|
|
|
|
// Fallback with minimal descriptions for tests when file reading fails
|
|
|
|
|
toolDescription = 'Execute shell commands';
|
|
|
|
|
toolParameterSchema = {
|
|
|
|
|
type: 'object',
|
|
|
|
|
properties: {
|
|
|
|
|
command: { type: 'string', description: 'Command to execute' },
|
|
|
|
|
description: { type: 'string', description: 'Command description' },
|
|
|
|
|
directory: { type: 'string', description: 'Working directory' },
|
|
|
|
|
},
|
|
|
|
|
required: ['command'],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-24 18:03:33 -07:00
|
|
|
super(
|
|
|
|
|
ShellTool.Name,
|
|
|
|
|
toolDisplayName,
|
|
|
|
|
toolDescription,
|
|
|
|
|
toolParameterSchema,
|
2025-05-30 13:59:05 -07:00
|
|
|
false, // output is not markdown
|
|
|
|
|
true, // output can be updated
|
2025-04-24 18:03:33 -07:00
|
|
|
);
|
2025-04-25 14:05:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getDescription(params: ShellToolParams): string {
|
2025-04-27 18:57:10 -07:00
|
|
|
let description = `${params.command}`;
|
2025-04-30 12:27:56 -07:00
|
|
|
// append optional [in directory]
|
2025-04-29 15:31:46 -07:00
|
|
|
// note description is needed even if validation fails due to absolute path
|
2025-04-28 08:17:52 -07:00
|
|
|
if (params.directory) {
|
2025-04-30 12:27:56 -07:00
|
|
|
description += ` [in ${params.directory}]`;
|
2025-04-28 08:17:52 -07:00
|
|
|
}
|
|
|
|
|
// append optional (description), replacing any line breaks with spaces
|
2025-04-27 18:57:10 -07:00
|
|
|
if (params.description) {
|
|
|
|
|
description += ` (${params.description.replace(/\n/g, ' ')})`;
|
|
|
|
|
}
|
|
|
|
|
return description;
|
2025-04-25 14:05:58 -07:00
|
|
|
}
|
|
|
|
|
|
2025-04-27 18:57:10 -07:00
|
|
|
getCommandRoot(command: string): string | undefined {
|
|
|
|
|
return command
|
|
|
|
|
.trim() // remove leading and trailing whitespace
|
|
|
|
|
.replace(/[{}()]/g, '') // remove all grouping operators
|
|
|
|
|
.split(/[\s;&|]+/)[0] // split on any whitespace or separator or chaining operators and take first part
|
|
|
|
|
?.split(/[/\\]/) // split on any path separators (or return undefined if previous line was undefined)
|
|
|
|
|
.pop(); // take last part and return command root (or undefined if previous line was empty)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validateToolParams(params: ShellToolParams): string | null {
|
|
|
|
|
if (
|
|
|
|
|
!SchemaValidator.validate(
|
|
|
|
|
this.parameterSchema as Record<string, unknown>,
|
|
|
|
|
params,
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
return `Parameters failed schema validation.`;
|
|
|
|
|
}
|
|
|
|
|
if (!params.command.trim()) {
|
|
|
|
|
return 'Command cannot be empty.';
|
|
|
|
|
}
|
|
|
|
|
if (!this.getCommandRoot(params.command)) {
|
|
|
|
|
return 'Could not identify command root to obtain permission from user.';
|
|
|
|
|
}
|
|
|
|
|
if (params.directory) {
|
|
|
|
|
if (path.isAbsolute(params.directory)) {
|
|
|
|
|
return 'Directory cannot be absolute. Must be relative to the project root directory.';
|
|
|
|
|
}
|
|
|
|
|
const directory = path.resolve(
|
|
|
|
|
this.config.getTargetDir(),
|
|
|
|
|
params.directory,
|
|
|
|
|
);
|
|
|
|
|
if (!fs.existsSync(directory)) {
|
|
|
|
|
return 'Directory must exist.';
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-25 14:05:58 -07:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async shouldConfirmExecute(
|
|
|
|
|
params: ShellToolParams,
|
2025-05-27 23:40:25 -07:00
|
|
|
_abortSignal: AbortSignal,
|
2025-04-25 14:05:58 -07:00
|
|
|
): Promise<ToolCallConfirmationDetails | false> {
|
2025-04-27 18:57:10 -07:00
|
|
|
if (this.validateToolParams(params)) {
|
|
|
|
|
return false; // skip confirmation, execute call will fail immediately
|
|
|
|
|
}
|
|
|
|
|
const rootCommand = this.getCommandRoot(params.command)!; // must be non-empty string post-validation
|
2025-04-25 14:05:58 -07:00
|
|
|
if (this.whitelist.has(rootCommand)) {
|
2025-04-27 18:57:10 -07:00
|
|
|
return false; // already approved and whitelisted
|
2025-04-25 14:05:58 -07:00
|
|
|
}
|
|
|
|
|
const confirmationDetails: ToolExecuteConfirmationDetails = {
|
2025-05-22 06:00:36 +00:00
|
|
|
type: 'exec',
|
2025-04-25 14:05:58 -07:00
|
|
|
title: 'Confirm Shell Command',
|
|
|
|
|
command: params.command,
|
|
|
|
|
rootCommand,
|
|
|
|
|
onConfirm: async (outcome: ToolConfirmationOutcome) => {
|
|
|
|
|
if (outcome === ToolConfirmationOutcome.ProceedAlways) {
|
|
|
|
|
this.whitelist.add(rootCommand);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
return confirmationDetails;
|
2025-04-24 18:03:33 -07:00
|
|
|
}
|
|
|
|
|
|
2025-05-09 23:29:02 -07:00
|
|
|
async execute(
|
|
|
|
|
params: ShellToolParams,
|
|
|
|
|
abortSignal: AbortSignal,
|
2025-05-30 01:58:09 -07:00
|
|
|
updateOutput?: (chunk: string) => void,
|
2025-05-09 23:29:02 -07:00
|
|
|
): Promise<ToolResult> {
|
2025-04-27 18:57:10 -07:00
|
|
|
const validationError = this.validateToolParams(params);
|
|
|
|
|
if (validationError) {
|
|
|
|
|
return {
|
|
|
|
|
llmContent: [
|
|
|
|
|
`Command rejected: ${params.command}`,
|
|
|
|
|
`Reason: ${validationError}`,
|
|
|
|
|
].join('\n'),
|
|
|
|
|
returnDisplay: `Error: ${validationError}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-08 15:42:49 -07:00
|
|
|
if (abortSignal.aborted) {
|
|
|
|
|
return {
|
|
|
|
|
llmContent: 'Command was cancelled by user before it could start.',
|
|
|
|
|
returnDisplay: 'Command cancelled by user.',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-06 10:44:40 -07:00
|
|
|
// wrap command to append subprocess pids (via pgrep) to temporary file
|
|
|
|
|
const tempFileName = `shell_pgrep_${crypto.randomBytes(6).toString('hex')}.tmp`;
|
|
|
|
|
const tempFilePath = path.join(os.tmpdir(), tempFileName);
|
|
|
|
|
|
2025-04-27 18:57:10 -07:00
|
|
|
let command = params.command.trim();
|
|
|
|
|
if (!command.endsWith('&')) command += ';';
|
2025-05-28 14:45:46 -07:00
|
|
|
command = `{ ${command} }; __code=$?; pgrep -g 0 >${tempFilePath} 2>&1; exit $__code;`;
|
2025-04-27 18:57:10 -07:00
|
|
|
|
|
|
|
|
// spawn command in specified directory (or project root if not specified)
|
|
|
|
|
const shell = spawn('bash', ['-c', command], {
|
|
|
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
|
|
|
detached: true, // ensure subprocess starts its own process group (esp. in Linux)
|
|
|
|
|
cwd: path.resolve(this.config.getTargetDir(), params.directory || ''),
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-28 14:45:46 -07:00
|
|
|
let exited = false;
|
2025-04-27 18:57:10 -07:00
|
|
|
let stdout = '';
|
|
|
|
|
let output = '';
|
2025-05-30 01:58:09 -07:00
|
|
|
let lastUpdateTime = Date.now();
|
|
|
|
|
|
|
|
|
|
const appendOutput = (str: string) => {
|
|
|
|
|
output += str;
|
|
|
|
|
if (
|
|
|
|
|
updateOutput &&
|
|
|
|
|
Date.now() - lastUpdateTime > OUTPUT_UPDATE_INTERVAL_MS
|
|
|
|
|
) {
|
|
|
|
|
updateOutput(output);
|
|
|
|
|
lastUpdateTime = Date.now();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-27 18:57:10 -07:00
|
|
|
shell.stdout.on('data', (data: Buffer) => {
|
2025-05-28 14:45:46 -07:00
|
|
|
// continue to consume post-exit for background processes
|
|
|
|
|
// removing listeners can overflow OS buffer and block subprocesses
|
|
|
|
|
// destroying (e.g. shell.stdout.destroy()) can terminate subprocesses via SIGPIPE
|
|
|
|
|
if (!exited) {
|
2025-06-02 14:50:12 -07:00
|
|
|
const str = stripAnsi(data.toString());
|
2025-05-28 14:45:46 -07:00
|
|
|
stdout += str;
|
2025-05-30 01:58:09 -07:00
|
|
|
appendOutput(str);
|
2025-05-27 15:40:18 -07:00
|
|
|
}
|
2025-04-27 18:57:10 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let stderr = '';
|
|
|
|
|
shell.stderr.on('data', (data: Buffer) => {
|
2025-05-28 14:45:46 -07:00
|
|
|
if (!exited) {
|
2025-06-02 14:50:12 -07:00
|
|
|
const str = stripAnsi(data.toString());
|
2025-05-28 14:45:46 -07:00
|
|
|
stderr += str;
|
2025-05-30 01:58:09 -07:00
|
|
|
appendOutput(str);
|
2025-04-27 18:57:10 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let error: Error | null = null;
|
|
|
|
|
shell.on('error', (err: Error) => {
|
|
|
|
|
error = err;
|
2025-05-21 09:31:13 -07:00
|
|
|
// remove wrapper from user's command in error message
|
|
|
|
|
error.message = error.message.replace(command, params.command);
|
2025-04-27 18:57:10 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let code: number | null = null;
|
2025-05-09 23:29:02 -07:00
|
|
|
let processSignal: NodeJS.Signals | null = null;
|
2025-05-28 14:45:46 -07:00
|
|
|
const exitHandler = (
|
2025-05-09 23:29:02 -07:00
|
|
|
_code: number | null,
|
|
|
|
|
_signal: NodeJS.Signals | null,
|
|
|
|
|
) => {
|
2025-05-28 14:45:46 -07:00
|
|
|
exited = true;
|
2025-05-09 23:29:02 -07:00
|
|
|
code = _code;
|
|
|
|
|
processSignal = _signal;
|
|
|
|
|
};
|
2025-05-28 14:45:46 -07:00
|
|
|
shell.on('exit', exitHandler);
|
2025-05-09 23:29:02 -07:00
|
|
|
|
2025-05-30 01:35:03 -07:00
|
|
|
const abortHandler = async () => {
|
2025-05-30 00:46:43 -07:00
|
|
|
if (shell.pid && !exited) {
|
2025-05-09 23:29:02 -07:00
|
|
|
try {
|
2025-05-30 00:46:43 -07:00
|
|
|
// attempt to SIGTERM process group (negative PID)
|
2025-05-30 01:35:03 -07:00
|
|
|
// fall back to SIGKILL (to group) after 200ms
|
2025-05-09 23:29:02 -07:00
|
|
|
process.kill(-shell.pid, 'SIGTERM');
|
2025-05-30 01:35:03 -07:00
|
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
|
|
|
if (shell.pid && !exited) {
|
|
|
|
|
process.kill(-shell.pid, 'SIGKILL');
|
|
|
|
|
}
|
2025-05-09 23:29:02 -07:00
|
|
|
} catch (_e) {
|
2025-05-30 00:46:43 -07:00
|
|
|
// if group kill fails, fall back to killing just the main process
|
2025-05-09 23:29:02 -07:00
|
|
|
try {
|
2025-05-30 00:46:43 -07:00
|
|
|
if (shell.pid) {
|
|
|
|
|
shell.kill('SIGKILL');
|
|
|
|
|
}
|
|
|
|
|
} catch (_e) {
|
|
|
|
|
console.error(`failed to kill shell process ${shell.pid}: ${_e}`);
|
2025-05-09 23:29:02 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
abortSignal.addEventListener('abort', abortHandler);
|
2025-04-27 18:57:10 -07:00
|
|
|
|
|
|
|
|
// wait for the shell to exit
|
2025-06-05 06:40:33 -07:00
|
|
|
try {
|
|
|
|
|
await new Promise((resolve) => shell.on('exit', resolve));
|
|
|
|
|
} finally {
|
|
|
|
|
abortSignal.removeEventListener('abort', abortHandler);
|
|
|
|
|
}
|
2025-05-09 23:29:02 -07:00
|
|
|
|
2025-05-06 10:44:40 -07:00
|
|
|
// parse pids (pgrep output) from temporary file and remove it
|
|
|
|
|
const backgroundPIDs: number[] = [];
|
|
|
|
|
if (fs.existsSync(tempFilePath)) {
|
|
|
|
|
const pgrepLines = fs
|
|
|
|
|
.readFileSync(tempFilePath, 'utf8')
|
|
|
|
|
.split('\n')
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
for (const line of pgrepLines) {
|
|
|
|
|
if (!/^\d+$/.test(line)) {
|
|
|
|
|
console.error(`pgrep: ${line}`);
|
|
|
|
|
}
|
|
|
|
|
const pid = Number(line);
|
|
|
|
|
// exclude the shell subprocess pid
|
|
|
|
|
if (pid !== shell.pid) {
|
|
|
|
|
backgroundPIDs.push(pid);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fs.unlinkSync(tempFilePath);
|
|
|
|
|
} else {
|
2025-05-09 23:29:02 -07:00
|
|
|
if (!abortSignal.aborted) {
|
|
|
|
|
console.error('missing pgrep output');
|
|
|
|
|
}
|
2025-05-06 10:44:40 -07:00
|
|
|
}
|
|
|
|
|
|
2025-05-09 23:29:02 -07:00
|
|
|
let llmContent = '';
|
|
|
|
|
if (abortSignal.aborted) {
|
2025-05-27 13:47:40 -07:00
|
|
|
llmContent = 'Command was cancelled by user before it could complete.';
|
|
|
|
|
if (output.trim()) {
|
|
|
|
|
llmContent += ` Below is the output (on stdout and stderr) before it was cancelled:\n${output}`;
|
|
|
|
|
} else {
|
|
|
|
|
llmContent += ' There was no output before it was cancelled.';
|
|
|
|
|
}
|
2025-05-09 23:29:02 -07:00
|
|
|
} else {
|
|
|
|
|
llmContent = [
|
|
|
|
|
`Command: ${params.command}`,
|
|
|
|
|
`Directory: ${params.directory || '(root)'}`,
|
|
|
|
|
`Stdout: ${stdout || '(empty)'}`,
|
|
|
|
|
`Stderr: ${stderr || '(empty)'}`,
|
|
|
|
|
`Error: ${error ?? '(none)'}`,
|
|
|
|
|
`Exit Code: ${code ?? '(none)'}`,
|
|
|
|
|
`Signal: ${processSignal ?? '(none)'}`,
|
|
|
|
|
`Background PIDs: ${backgroundPIDs.length ? backgroundPIDs.join(', ') : '(none)'}`,
|
2025-05-30 23:25:44 -07:00
|
|
|
`Process Group PGID: ${shell.pid ?? '(none)'}`,
|
2025-05-09 23:29:02 -07:00
|
|
|
].join('\n');
|
|
|
|
|
}
|
2025-04-28 15:05:36 -07:00
|
|
|
|
fix(shell): Improve error reporting for shell command failures
This commit enhances the tool to provide more informative feedback to the user when a shell command fails, especially in non-debug mode.
Previously, if a command terminated due to a signal (e.g., SIGPIPE during a with no upstream) or failed without producing stdout/stderr, the user would see no output, making it difficult to diagnose the issue.
Changes:
- Modified to update the logic.
- If a command produces no direct output but results in an error, signal, non-zero exit code, or user cancellation, a concise message indicating this outcome is now shown in .
- Utilized the existing utility from for consistent error message formatting, which also resolved previous TypeScript type inference issues.
This ensures users receive clearer feedback on command execution status, improving the tool's usability and aiding in troubleshooting.
Fixes https://b.corp.google.com/issues/417998119
2025-05-18 00:23:57 -07:00
|
|
|
let returnDisplayMessage = '';
|
|
|
|
|
if (this.config.getDebugMode()) {
|
|
|
|
|
returnDisplayMessage = llmContent;
|
|
|
|
|
} else {
|
|
|
|
|
if (output.trim()) {
|
|
|
|
|
returnDisplayMessage = output;
|
|
|
|
|
} else {
|
|
|
|
|
// Output is empty, let's provide a reason if the command failed or was cancelled
|
|
|
|
|
if (abortSignal.aborted) {
|
|
|
|
|
returnDisplayMessage = 'Command cancelled by user.';
|
|
|
|
|
} else if (processSignal) {
|
|
|
|
|
returnDisplayMessage = `Command terminated by signal: ${processSignal}`;
|
|
|
|
|
} else if (error) {
|
|
|
|
|
// If error is not null, it's an Error object (or other truthy value)
|
|
|
|
|
returnDisplayMessage = `Command failed: ${getErrorMessage(error)}`;
|
|
|
|
|
} else if (code !== null && code !== 0) {
|
|
|
|
|
returnDisplayMessage = `Command exited with code: ${code}`;
|
|
|
|
|
}
|
|
|
|
|
// If output is empty and command succeeded (code 0, no error/signal/abort),
|
|
|
|
|
// returnDisplayMessage will remain empty, which is fine.
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-28 15:05:36 -07:00
|
|
|
|
fix(shell): Improve error reporting for shell command failures
This commit enhances the tool to provide more informative feedback to the user when a shell command fails, especially in non-debug mode.
Previously, if a command terminated due to a signal (e.g., SIGPIPE during a with no upstream) or failed without producing stdout/stderr, the user would see no output, making it difficult to diagnose the issue.
Changes:
- Modified to update the logic.
- If a command produces no direct output but results in an error, signal, non-zero exit code, or user cancellation, a concise message indicating this outcome is now shown in .
- Utilized the existing utility from for consistent error message formatting, which also resolved previous TypeScript type inference issues.
This ensures users receive clearer feedback on command execution status, improving the tool's usability and aiding in troubleshooting.
Fixes https://b.corp.google.com/issues/417998119
2025-05-18 00:23:57 -07:00
|
|
|
return { llmContent, returnDisplay: returnDisplayMessage };
|
2025-04-24 18:03:33 -07:00
|
|
|
}
|
|
|
|
|
}
|