Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions 13_sandboxes/sandbox_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# ---
# cmd: ["python", "13_sandboxes/sandbox_agent.py"]
# pytest: false
# ---

# # Run Claude Code in a Modal Sandbox

# This example demonstrates how to run Claude Code in a Modal
# [Sandbox](https://modal.com/docs/guide/sandbox) to analyze a GitHub repository.
# The Sandbox provides an isolated environment where the agent can safely execute code
# and examine files.

import modal

app = modal.App.lookup("example-sandbox-agent", create_if_missing=True)

# First we create a custom Image that has Claude Code installed.
image = (
modal.Image.debian_slim(python_version="3.12")
.apt_install("curl", "git")
.run_commands(
"curl -fsSL https://claude.ai/install.sh | bash",
)
.env(
{
"PATH": "/root/.local/bin:$PATH",
"USE_BUILTIN_RIPGREP": "0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment about why these are necessary?

Copy link
Contributor Author

@ehdr ehdr Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, also good catch, USE_BUILTIN_RIPGREP is only needed on alpine. Pushed a fix now.

}
)
)

with modal.enable_output():
sandbox = modal.Sandbox.create(app=app, image=image)
print(f"Sandbox ID: {sandbox.object_id}")

# Next we'll clone a repository that Claude Code will work on.
repo_url = "https://github.com/modal-labs/modal-examples"
git_ps = sandbox.exec("git", "clone", repo_url, "/repo")
git_ps.wait()
print(f"Cloned '{repo_url}' into /repo.")

# Finally we'll run Claude Code to analyze the repository.
claude_cmd = [
"claude",
"-p",
"Summarize what this repository is about. Don't modify any files.",
]

print("\nRunning command:", claude_cmd)

claude_ps = sandbox.exec(
*claude_cmd,
pty=True, # Adding a PTY is important, since Claude requires it!
secrets=[
modal.Secret.from_name("anthropic-secret", required_keys=["ANTHROPIC_API_KEY"])
],
workdir="/repo",
)
claude_ps.wait()

print("\nAgent stdout:\n")
print(claude_ps.stdout.read())

stderr = claude_ps.stderr.read()
if stderr != "":
print("Agent stderr:", stderr)