-
Notifications
You must be signed in to change notification settings - Fork 3
Use a claude and gemini compatible agentic sandbox #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
740efbe
2d618e8
ebdc24c
f7bf680
3fe781c
ecb07a6
0e3b159
50710ad
9a16a9d
672bf89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| GEMINI.md |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,155 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Script to run an AI agent (Gemini CLI or Claude Code) in a Docker sandbox.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import argparse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _seed_config_file(path: str, content: bytes) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Create path with content and mode 0o600, skipping silently if it already exists.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except FileExistsError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.write(fd, content) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finally: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.close(fd) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+16
to
+19
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | |
| os.write(fd, content) | |
| finally: | |
| os.close(fd) | |
| with os.fdopen(fd, "wb") as file_obj: | |
| file_obj.write(content) |
Copilot
AI
Apr 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Docker image name is agent-specific (cc-protocol-{agent}-sandbox), but the Dockerfile installs both CLIs unconditionally. After building one image, running the other agent will fail unless its separate image is also built, and you end up with duplicated identical images. Consider either using a single shared image name (since both CLIs are present) or making the Dockerfile conditional on AGENT so per-agent images are meaningful.
Copilot
AI
Apr 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docker build is passing --build-arg AGENT=..., but the Dockerfile doesn't use AGENT anywhere, so the build arg (and ARG AGENT=gemini) are currently redundant and can confuse readers about what varies between the ...-gemini-sandbox and ...-claude-sandbox images. Either remove the build arg/ARG entirely, or actually use it (e.g., to conditionally install only the selected CLI) so the tag difference reflects a real image difference.
Copilot
AI
Apr 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--network host is enabled unconditionally for Gemini runs. This reduces isolation and is not supported/behaves differently on some Docker setups (e.g., Docker Desktop), which can make the sandbox unusable outside Linux. Consider making host networking opt-in via a flag/env var and defaulting to bridge networking (using host.docker.internal/explicit port forwards if needed).
Copilot
AI
Apr 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If ~/.claude.json already exists but is a directory (e.g., from a previous Docker run where the file path was mounted before being created), this will attempt to create/open it as a file and raise IsADirectoryError. Add an explicit os.path.isdir(host_claude_json) check and fail with a clear remediation message (or replace it) before proceeding.
| # other local users on multi-user systems. | |
| # other local users on multi-user systems. | |
| if os.path.isdir(host_claude_json): | |
| sys.exit( | |
| f"{host_claude_json} exists as a directory, but Claude expects this path " | |
| "to be a file. Remove or rename that directory and rerun this script so " | |
| "it can create an empty ~/.claude.json file." | |
| ) |
Copilot
AI
Apr 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new sandbox does not pass through ANTHROPIC_API_KEY into the container when running the claude agent. The repository’s existing workflow/docs use ANTHROPIC_API_KEY for Claude authentication (see scripts/claude-sandbox.py / CONTRIBUTING.md), so this is a breaking behavior change unless everyone is expected to use mounted OAuth/session state. Consider forwarding ANTHROPIC_API_KEY when it is present (and/or printing a clear error/instruction if neither env var nor auth state exists).
| else: | |
| host_claude_dir = os.path.expanduser("~/.claude") | |
| host_claude_json = os.path.expanduser("~/.claude.json") | |
| os.makedirs(host_claude_dir, mode=0o700, exist_ok=True) | |
| # Ensure the file exists on the host so Docker doesn't create it as a directory. | |
| # Use os.open with restrictive permissions to avoid exposing credentials to | |
| # other local users on multi-user systems. | |
| if not os.path.exists(host_claude_json): | |
| fd = os.open(host_claude_json, os.O_CREAT | os.O_WRONLY, 0o600) | |
| os.close(fd) | |
| run_args.extend(["-v", f"{host_claude_dir}:/home/vscode/.claude"]) | |
| run_args.extend(["-v", f"{host_claude_json}:/home/vscode/.claude.json"]) | |
| else: | |
| anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY") | |
| host_claude_dir = os.path.expanduser("~/.claude") | |
| host_claude_json = os.path.expanduser("~/.claude.json") | |
| os.makedirs(host_claude_dir, mode=0o700, exist_ok=True) | |
| claude_dir_has_state = any(os.scandir(host_claude_dir)) | |
| # Ensure the file exists on the host so Docker doesn't create it as a directory. | |
| # Use os.open with restrictive permissions to avoid exposing credentials to | |
| # other local users on multi-user systems. | |
| if not os.path.exists(host_claude_json): | |
| fd = os.open(host_claude_json, os.O_CREAT | os.O_WRONLY, 0o600) | |
| os.close(fd) | |
| claude_json_has_state = os.path.getsize(host_claude_json) > 0 | |
| run_args.extend(["-v", f"{host_claude_dir}:/home/vscode/.claude"]) | |
| run_args.extend(["-v", f"{host_claude_json}:/home/vscode/.claude.json"]) | |
| if anthropic_api_key: | |
| run_args.extend(["-e", f"ANTHROPIC_API_KEY={anthropic_api_key}"]) | |
| elif not claude_dir_has_state and not claude_json_has_state: | |
| print( | |
| "Warning: Claude authentication was not found. Set " | |
| "ANTHROPIC_API_KEY in your environment or authenticate via " | |
| "~/.claude / ~/.claude.json before running the sandbox.", | |
| file=sys.stderr, | |
| ) |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sandbox docs no longer mention API keys (good), but they also don't explain the new OAuth-based auth flow (e.g., that the script mounts
~/.gemini/~/.claude{,.json}from the host and that first-run will prompt a login). Adding a brief note here would prevent users from thinking the sandbox is broken when the agent asks for auth.