Skip to main content
Coding tools give agents shell access and file editing. Like computer tools, each provider has its own spec.

Quick Reference

Shell tools execute commands in a persistent bash session:
ToolAgentFeatures
BashToolClaudePersistent, manual restart
ShellToolOpenAIAuto-restart, dynamic timeout
GeminiShellToolGeminiSimple execution
Editor tools modify files:
ToolAgentStyle
EditToolClaudestr_replace based
ApplyPatchToolOpenAIUnified diff
GeminiEditToolGeminiInstruction-based

BashTool (Claude)

Persistent bash shell. Session survives across calls. Agent must manually restart on timeout.
from hud.tools import BashTool

bash = BashTool()
# Execute command
result = await bash(command="ls -la")

# Chain commands (session persists)
await bash(command="cd /app")
await bash(command="npm install")

# Restart if session dies
await bash(restart=True)
Uses native bash_20250124 API.

ShellTool (OpenAI)

Auto-restarts on error. Supports multiple commands with per-command timeout.
from hud.tools.coding import ShellTool

shell = ShellTool()
result = await shell(
    commands=["cd /app", "npm install", "npm run build"],
    timeout_ms=60000,
)

for output in result.output:
    print(f"stdout: {output.stdout}")
    print(f"exit: {output.outcome.exit_code}")
Uses native shell API.

GeminiShellTool

Simple command execution for Gemini and generic agents.
from hud.tools.coding import GeminiShellTool

shell = GeminiShellTool()
result = await shell(command="python script.py", timeout=120)

EditTool (Claude)

File editor using str_replace. Maintains undo history.
from hud.tools import EditTool

editor = EditTool()
Commands: view, create, str_replace, insert, undo_edit
# View file
await editor(command="view", path="/app/main.py", view_range=[1, 50])

# View directory
await editor(command="view", path="/app")

# Create file
await editor(
    command="create",
    path="/app/new.py",
    file_text="def hello():\n    print('Hello!')",
)

# Replace text (old_str must be unique in file)
await editor(
    command="str_replace",
    path="/app/main.py",
    old_str="print('old')",
    new_str="print('new')",
)

# Insert at line
await editor(
    command="insert",
    path="/app/main.py",
    insert_line=10,
    new_str="# New comment\n",
)

# Undo last edit
await editor(command="undo_edit", path="/app/main.py")
Uses native text_editor_20250728 API. Paths must be absolute.

ApplyPatchTool (OpenAI)

Unified diff format for file modifications.
from hud.tools.coding import ApplyPatchTool

patcher = ApplyPatchTool()

patch = """--- a/main.py
+++ b/main.py
@@ -10,7 +10,7 @@
 def greet(name):
-    print(f"Hello, {name}!")
+    print(f"Welcome, {name}!")
     return True
"""

result = await patcher(patch=patch)

GeminiEditTool

Instruction-based editing for Gemini.
from hud.tools.coding import GeminiEditTool

editor = GeminiEditTool()

# Natural language instruction
await editor(
    file_path="/app/main.py",
    instruction="Add a docstring to the greet function",
)

# Direct replacement
await editor(
    file_path="/app/main.py",
    old_content="def greet():",
    new_content="def greet(name: str):",
)

Role Exclusion

Shell tools share role="shell". Editor tools share role="editor". Only one per role can be active natively—prevents conflicts.

Typical Setup

For Claude:
from hud import Environment
from hud.tools import BashTool, EditTool

env = Environment("coding-env")
env.add_tool(BashTool())
env.add_tool(EditTool())
For OpenAI:
from hud import Environment
from hud.tools.coding import ShellTool, ApplyPatchTool

env = Environment("coding-env")
env.add_tool(ShellTool())
env.add_tool(ApplyPatchTool())

Customizing

Use hooks for simple validation:
from hud.tools import BashTool
from hud.tools.types import ToolError

bash = BashTool()

@bash.before
async def block_dangerous(command: str | None = None, **kwargs):
    if command:
        for blocked in ["rm -rf /", "sudo", "curl | sh"]:
            if blocked in command:
                raise ToolError(f"Blocked: {blocked}")

env.add_tool(bash)
Read-only editor:
from hud.tools import EditTool
from hud.tools.types import ToolError

editor = EditTool()

@editor.before
async def read_only(command: str = "", **kwargs):
    if command != "view":
        raise ToolError("Read-only environment")

env.add_tool(editor)
Or subclass for more complex logic:
from typing import Any
from mcp.types import ContentBlock
from hud.tools import BashTool
from hud.tools.types import ToolError

class AuditedBashTool(BashTool):
    def __init__(self):
        super().__init__()
        self.command_history: list[str] = []
    
    async def __call__(
        self, command: str | None = None, restart: bool = False
    ) -> list[ContentBlock]:
        if command:
            self.command_history.append(command)
        return await super().__call__(command, restart)