← Back to daily report

agents-and-tools/tool-use/bash-tool.md

Changed on 2026-03-03 18:21:53 EST

+2 lines added
-0 lines removed
Visual Diff
# Bash tool¶

---¶

The bash tool enables Claude to execute shell commands in a persistent bash session, allowing system operations, script execution, and command-line automation. Shell access is a foundational agent capability. On [Terminal-Bench 2.0](https://github.com/terminal-bench/terminal-bench), a benchmark that evaluates real-world terminal tasks using shell-only validation, Claude shows strong performance gains with access to a persistent bash session.¶

## Overview¶

The bash tool provides Claude with:¶
- Persistent bash session that maintains state¶
- Ability to run any shell command¶
- Access to environment variables and working directory¶
- Command chaining and scripting capabilities¶

## Model compatibility¶

| Model | Tool Version |¶
|-------|--------------|¶
| Claude 4 models and Sonnet 3.7 ([deprecated](/docs/en/about-claude/model-deprecations)) | `bash_20250124` |¶

<Warning>¶
Older tool versions are not guaranteed to be backwards-compatible with newer models. Always use the tool version that corresponds to your model version.¶
</Warning>¶

## Use cases¶

- **Development workflows:** Run build commands, tests, and development tools¶
- **System automation:** Execute scripts, manage files, automate tasks¶
- **Data processing:** Process files, run analysis scripts, manage datasets¶
- **Environment setup:** Install packages, configure environments¶

## Quick start¶

<CodeGroup>¶
```python Python¶
import anthropic¶

client = anthropic.Anthropic()¶

response = client.messages.create(¶
model="claude-opus-4-6",¶
max_tokens=1024,¶
tools=[{"type": "bash_20250124", "name": "bash"}],¶
messages=[¶
{"role": "user", "content": "List all Python files in the current directory."}¶
],¶
)¶
```¶

```bash Shell¶
curl https://api.anthropic.com/v1/messages \¶
-H "content-type: application/json" \¶
-H "x-api-key: $ANTHROPIC_API_KEY" \¶
-H "anthropic-version: 2023-06-01" \¶
-d '{¶
"model": "claude-opus-4-6",¶
"max_tokens": 1024,¶
"tools": [¶
{¶
"type": "bash_20250124",¶
"name": "bash"¶
}¶
],¶
"messages": [¶
{¶
"role": "user",¶
"content": "List all Python files in the current directory."¶
}¶
]¶
}'¶
```¶
</CodeGroup>¶

## How it works¶

The bash tool maintains a persistent session:¶

1. Claude determines what command to run¶
2. You execute the command in a bash shell¶
3. Return the output (stdout and stderr) to Claude¶
4. Session state persists between commands (environment variables, working directory)¶

## Parameters¶

| Parameter | Required | Description |¶
|-----------|----------|-------------|¶
| `command` | Yes* | The bash command to run |¶
| `restart` | No | Set to `true` to restart the bash session |¶

*Required unless using `restart`¶

<section title="Example usage">¶

Run a command:¶

```json¶
{¶
"command": "ls -la *.py"¶
}¶
```¶

Restart the session:¶

```json¶
{¶
"restart": true¶
}¶
```¶

</section>¶

## Example: Multi-step automation¶

Claude can chain commands to complete complex tasks:¶

```python
nocheck
# User request¶
"Install the requests library and create a simple Python script that fetches a joke from an API, then run it."¶

# Claude's tool uses:¶
# 1. Install package¶
{"command": "pip install requests"}¶

# 2. Create script¶
{¶
"command": "cat > fetch_joke.py << 'EOF'\nimport requests\nresponse = requests.get('https://official-joke-api.appspot.com/random_joke')\njoke = response.json()\nprint(f\"Setup: {joke['setup']}\")\nprint(f\"Punchline: {joke['punchline']}\")\nEOF"¶
}¶

# 3. Run script¶
{"command": "python fetch_joke.py"}¶
```¶

The session maintains state between commands, so files created in step 2 are available in step 3.¶

***¶

## Implement the bash tool¶

The bash tool is implemented as a schema-less tool. When using this tool, you don't need to provide an input schema as with other tools; the schema is built into Claude's model and can't be modified.¶

<Steps>¶
<Step title="Set up a bash environment">¶
Create a persistent bash session that Claude can interact with:¶

```python nocheck
import subprocess¶
import threading¶
import queue¶


class BashSession:¶
def __init__(self):¶
self.process = subprocess.Popen(¶
["/bin/bash"],¶
stdin=subprocess.PIPE,¶
stdout=subprocess.PIPE,¶
stderr=subprocess.PIPE,¶
text=True,¶
bufsize=0,¶
)¶
self.output_queue = queue.Queue()¶
self.error_queue = queue.Queue()¶
self._start_readers()¶
```¶
</Step>¶
<Step title="Handle command execution">¶
Create a function to execute commands and capture output:¶

```python nocheck
def execute_command(self, command):¶
# Send command to bash¶
self.process.stdin.write(command + "\n")¶
self.process.stdin.flush()¶

# Capture output with timeout¶
output = self._read_output(timeout=10)¶
return output¶
```¶
</Step>¶
<Step title="Process Claude's tool calls">¶
Extract and execute commands from Claude's responses:¶

```python nocheck
for content in response.content:¶
if content.type == "tool_use" and content.name == "bash":¶
if content.input.get("restart"):¶
bash_session.restart()¶
result = "Bash session restarted"¶
else:¶
command = content.input.get("command")¶
result = bash_session.execute_command(command)¶

# Return result to Claude¶
tool_result = {¶
"type": "tool_result",¶
"tool_use_id": content.id,¶
"content": result,¶
}¶
```¶
</Step>¶
<Step title="Implement safety measures">¶
Add validation and restrictions:¶
```python¶
def validate_command(command):¶
# Block dangerous commands¶
dangerous_patterns = ["rm -rf /", "format", ":(){:|:&};:"]¶
for pattern in dangerous_patterns:¶
if pattern in command:¶
return False, f"Command contains dangerous pattern: {pattern}"¶

# Add more validation as needed¶
return True, None¶
```¶
</Step>¶
</Steps>¶

### Handle errors¶

When implementing the bash tool, handle various error scenarios:¶

<section title="Command execution timeout">¶

If a command takes too long to execute:¶

```json¶
{¶
"role": "user",¶
"content": [¶
{¶
"type": "tool_result",¶
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",¶
"content": "Error: Command timed out after 30 seconds",¶
"is_error": true¶
}¶
]¶
}¶
```¶

</section>¶

<section title="Command not found">¶

If a command doesn't exist:¶

```json¶
{¶
"role": "user",¶
"content": [¶
{¶
"type": "tool_result",¶
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",¶
"content": "bash: nonexistentcommand: command not found",¶
"is_error": true¶
}¶
]¶
}¶
```¶

</section>¶

<section title="Permission denied">¶

If there are permission issues:¶

```json¶
{¶
"role": "user",¶
"content": [¶
{¶
"type": "tool_result",¶
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",¶
"content": "bash: /root/sensitive-file: Permission denied",¶
"is_error": true¶
}¶
]¶
}¶
```¶

</section>¶

### Follow implementation best practices¶

<section title="Use command timeouts">¶

Implement timeouts to prevent hanging commands:¶

```python nocheck
def execute_with_timeout(command, timeout=30):¶
try:¶
result = subprocess.run(¶
command, shell=True, capture_output=True, text=True, timeout=timeout¶
)¶
return result.stdout + result.stderr¶
except subprocess.TimeoutExpired:¶
return f"Command timed out after {timeout} seconds"¶
```¶

</section>¶

<section title="Maintain session state">¶

Keep the bash session persistent to maintain environment variables and working directory:¶
```python¶
# Commands run in the same session maintain state¶
commands = [¶
"cd /tmp",¶
"echo 'Hello' > test.txt",¶
"cat test.txt", # This works because we're still in /tmp¶
]¶
```¶

</section>¶

<section title="Handle large outputs">¶

Truncate very large outputs to prevent token limit issues:¶
```python¶
def truncate_output(output, max_lines=100):¶
lines = output.split("\n")¶
if len(lines) > max_lines:¶
truncated = "\n".join(lines[:max_lines])¶
return f"{truncated}\n\n... Output truncated ({len(lines)} total lines) ..."¶
return output¶
```¶

</section>¶

<section title="Log all commands">¶

Keep an audit trail of executed commands:¶
```python¶
import logging¶


def log_command(command, output, user_id):¶
logging.info(f"User {user_id} executed: {command}")¶
logging.info(f"Output: {output[:200]}...") # Log first 200 chars¶
```¶

</section>¶

<section title="Sanitize outputs">¶

Remove sensitive information from command outputs:¶
```python¶
def sanitize_output(output):¶
# Remove potential secrets or credentials¶
import re¶

# Example: Remove AWS credentials¶
output = re.sub(r"aws_access_key_id\s*=\s*\S+", "aws_access_key_id=***", output)¶
output = re.sub(¶
r"aws_secret_access_key\s*=\s*\S+", "aws_secret_access_key=***", output¶
)¶
return output¶
```¶

</section>¶

## Security¶

<Warning>¶
The bash tool provides direct system access. Implement these essential safety measures:¶
- Running in isolated environments (Docker/VM)¶
- Implementing command filtering and allowlists¶
- Setting resource limits (CPU, memory, disk)¶
- Logging all executed commands¶
</Warning>¶

### Key recommendations¶
- Use `ulimit` to set resource constraints¶
- Filter dangerous commands (`sudo`, `rm -rf`, etc.)¶
- Run with minimal user permissions¶
- Monitor and log all command execution¶

## Pricing¶

The bash tool adds **245 input tokens** to your API calls.¶

Additional tokens are consumed by:¶
- Command outputs (stdout/stderr)¶
- Error messages¶
- Large file contents¶

See [tool use pricing](/docs/en/agents-and-tools/tool-use/overview#pricing) for complete pricing details.¶

## Common patterns¶

### Development workflows¶
- Running tests: `pytest && coverage report`¶
- Building projects: `npm install && npm run build`¶
- Git operations: `git status && git add . && git commit -m "message"`¶

#### Git-based checkpointing¶

Git serves as a structured recovery mechanism in long-running agent workflows, not just a way to save changes:¶

- **Capture a baseline:** Before any agent work begins, commit the current state. This is the known-good starting point.¶
- **Commit per feature:** Each completed feature gets its own commit. These serve as rollback points if something goes wrong later.¶
- **Reconstruct state at session start:** Read `git log` alongside a progress file to understand what has already been done and what comes next.¶
- **Revert on failure:** If work goes sideways, `git checkout` reverts to the last good commit instead of trying to debug a broken state.¶

### File operations¶
- Processing data: `wc -l *.csv && ls -lh *.csv`¶
- Searching files: `find . -name "*.py" | xargs grep "pattern"`¶
- Creating backups: `tar -czf backup.tar.gz ./data`¶

### System tasks¶
- Checking resources: `df -h && free -m`¶
- Process management: `ps aux | grep python`¶
- Environment setup: `export PATH=$PATH:/new/path && echo $PATH`¶

## Limitations¶

- **No interactive commands:** Cannot handle `vim`, `less`, or password prompts¶
- **No GUI applications:** Command-line only¶
- **Session scope:** Persists within conversation, lost between API calls¶
- **Output limits:** Large outputs may be truncated¶
- **No streaming:** Results returned after completion¶

## Combining with other tools¶

The bash tool is most powerful when combined with the [text editor](/docs/en/agents-and-tools/tool-use/text-editor-tool) and other tools.¶

<Note>¶
If you're also using the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool), Claude has access to two separate execution environments: your local bash session and Anthropic's sandboxed container. State is not shared between them. See [Using code execution with other execution tools](/docs/en/agents-and-tools/tool-use/code-execution-tool#using-code-execution-with-other-execution-tools) for guidance on prompting Claude to distinguish between environments.¶
</Note>¶

## Next steps¶

<CardGroup cols={2}>¶
<Card¶
title="Tool use overview"¶
icon="tool"¶
href="/docs/en/agents-and-tools/tool-use/overview"¶
>¶
Learn about tool use with Claude¶
</Card>¶

<Card¶
title="Text editor tool"¶
icon="file"¶
href="/docs/en/agents-and-tools/tool-use/text-editor-tool"¶
>¶
View and edit text files with Claude¶
</Card>¶
</CardGroup>

Unified Diff

--- a/agents-and-tools/tool-use/bash-tool.md
+++ b/agents-and-tools/tool-use/bash-tool.md
@@ -113,7 +113,7 @@
 
 Claude can chain commands to complete complex tasks:
 
-```python
+```python nocheck
 # User request
 "Install the requests library and create a simple Python script that fetches a joke from an API, then run it."
 
@@ -141,7 +141,8 @@
 <Steps>
   <Step title="Set up a bash environment">
     Create a persistent bash session that Claude can interact with:
-    ```python
+
+    ```python nocheck
     import subprocess
     import threading
     import queue
@@ -164,7 +165,8 @@
   </Step>
   <Step title="Handle command execution">
     Create a function to execute commands and capture output:
-    ```python
+
+    ```python nocheck
     def execute_command(self, command):
         # Send command to bash
         self.process.stdin.write(command + "\n")
@@ -177,7 +179,8 @@
   </Step>
   <Step title="Process Claude's tool calls">
     Extract and execute commands from Claude's responses:
-    ```python
+
+    ```python nocheck
     for content in response.content:
         if content.type == "tool_use" and content.name == "bash":
             if content.input.get("restart"):
@@ -280,7 +283,8 @@
 <section title="Use command timeouts">
 
 Implement timeouts to prevent hanging commands:
-```python
+
+```python nocheck
 def execute_with_timeout(command, timeout=30):
     try:
         result = subprocess.run(