Skip to content

shell_tool

code_context_agent.tools.shell_tool

Shell tool with STDIO capture and security hardening.

Commands are validated against an allowlist of read-only programs. Shell operators, path traversal, and write operations are blocked.

CommandResult

Bases: BaseModel

Result of a shell command execution.

success property

success

Command succeeded if exit code is 0 and no stderr.

status property

status

Status string for display.

shell

shell(
    command,
    work_dir=None,
    timeout=None,
    ignore_errors=False,
)

Execute shell commands with proper STDIO capture.

USE THIS TOOL: - For running read-only shell commands (ls, git log, wc, etc.) - When you need command output for analysis

DO NOT USE: - For reading file contents (use read_file_bounded instead) - For searching code (use rg_search instead) - For modifying files, network access, or running arbitrary scripts

Security: Commands are validated against an allowlist of read-only programs. Shell operators (pipes, redirects, chaining) are blocked. Git commands are restricted to read-only subcommands.

Parameters:

Name Type Description Default
command str | list[str]

Shell command string or list of commands to execute sequentially.

required
work_dir str | None

Working directory for command execution (default: current dir).

None
timeout int | None

Timeout in seconds for command execution (default: 900).

None
ignore_errors bool

If True, continue on errors and return success (default: False).

False

Returns:

Type Description
dict[str, Any]

Dict with status and content blocks.

Example

shell("ls -la") shell(["git status", "git diff"], work_dir="/repo")

Source code in src/code_context_agent/tools/shell_tool.py
@tool
def shell(
    command: str | list[str],
    work_dir: str | None = None,
    timeout: int | None = None,
    ignore_errors: bool = False,
) -> dict[str, Any]:
    """Execute shell commands with proper STDIO capture.

    USE THIS TOOL:
    - For running read-only shell commands (ls, git log, wc, etc.)
    - When you need command output for analysis

    DO NOT USE:
    - For reading file contents (use read_file_bounded instead)
    - For searching code (use rg_search instead)
    - For modifying files, network access, or running arbitrary scripts

    Security: Commands are validated against an allowlist of read-only programs.
    Shell operators (pipes, redirects, chaining) are blocked. Git commands are
    restricted to read-only subcommands.

    Args:
        command: Shell command string or list of commands to execute sequentially.
        work_dir: Working directory for command execution (default: current dir).
        timeout: Timeout in seconds for command execution (default: 900).
        ignore_errors: If True, continue on errors and return success (default: False).

    Returns:
        Dict with status and content blocks.

    Example:
        >>> shell("ls -la")
        >>> shell(["git status", "git diff"], work_dir="/repo")
    """
    timeout = timeout or DEFAULT_TIMEOUT
    work_dir = work_dir or str(Path.cwd())
    commands = [command] if isinstance(command, str) else command

    results: list[CommandResult] = []
    for cmd in commands:
        violation = _validate_command(cmd)
        if violation:
            logger.warning(f"Shell command blocked: {violation}")
            results.append(CommandResult(command=cmd, exit_code=-1, stdout="", stderr=violation))
            if not ignore_errors:
                break
            continue

        result = _execute(cmd, work_dir, timeout)
        results.append(result)
        if not result.success and not ignore_errors:
            break

    # Build response
    success_count = sum(1 for r in results if r.success)
    content = [
        {
            "text": f"Execution Summary:\nTotal commands: {len(results)}\n"
            f"Successful: {success_count}\nFailed: {len(results) - success_count}",
        },
    ]
    for r in results:
        parts = [f"Command: {r.command}", f"Status: {r.status}", f"Exit Code: {r.exit_code}"]
        if r.stdout:
            parts.append(f"Output:\n{r.stdout}")
        if r.stderr:
            parts.append(f"Error:\n{r.stderr}")
        content.append({"text": "\n".join(parts)})

    has_errors = any(not r.success for r in results)
    return {"status": "error" if has_errors and not ignore_errors else "success", "content": content}